React Native Expo: Authentication with Identity Server 4 - authentication

I am trying to use the sample code from expo documentation "https://docs.expo.io/guides/authentication/" for IdentityServer4. I have created a React template from ASP.NET Core Web Application. When I run the react native app to redirect to the login page of the ASP application I can login but the problem is the window does not close to return the token. It logs in and then I go to the home page of the ASP application. I believe I am missing the redirect URL or the client is not setup the right way. Can someone help me how to set this up? Or what part am I missing?
Here is my client set up from the ASP application from the appsettings.json:
"IdentityServer": {
"Clients": {
"AAA": {
"ClientId": "myclientid,
"ClientName": "Native Client (Code with PKCE)",
"RequireClientSecret": false,
"RedirectUris": [ "io.expo.auth/#username/Auth" ],
"AllowedGrantTypes": "GrantTypes.Code",
"RequirePkce": "true",
"AllowedScopes": ['openid', 'profile', 'email', 'offline_access'],
"AllowOfflineAccess": "true",
"Profile": "IdentityServerSPA",
"ClientSecrets": [
{
"Value": "secretvalue"
}
]
}
},
Below is my React Native app:
import * as React from 'react';
import { Button, Text, View } from 'react-native';
import * as AuthSession from 'expo-auth-session';
import * as WebBrowser from 'expo-web-browser';
WebBrowser.maybeCompleteAuthSession();
const useProxy = true;
const redirectUri = AuthSession.makeRedirectUri({
native: 'myApp://io.expo.auth/#username/Auth',
useProxy,
});
export default function App() {
const discovery = AuthSession.useAutoDiscovery('https://demo.identityserver.io');
// Create and load an auth request
const [request, result, promptAsync] = AuthSession.useAuthRequest(
{
clientId: 'myclientid',
redirectUri,
scopes: ['openid', 'profile', 'email', 'offline_access'],
},
discovery
);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Button title="Login!" disabled={!request} onPress={() => promptAsync({ useProxy })} />
{result && <Text>{JSON.stringify(result, null, 2)}</Text>}
</View>
);
}

Here is what you need to fix on ur code:
On appsettings.json change RedirectUris to be a list, result would be like this:
"IdentityServer": {
"Clients": {
"AAA": {
"ClientId": "myclientid,
"ClientName": "Native Client (Code with PKCE)",
"RequireClientSecret": false,
"RedirectUris": [ "io.expo.auth/#username/Auth" ],
"AllowedGrantTypes": "GrantTypes.Code",
"RequirePkce": "true",
"AllowedScopes": [ "api" ],
"AllowOfflineAccess": "true",
"Profile": "IdentityServerSPA",
"ClientSecrets": [
{
"Value": "secretvalue"
}
]
}
},
Read more here
Make sure URL listed on RedirectUris exist, and it match the value set for redirectUri on the native client. I'm no expo expert, you may use some sort of middleware but as far as I understand from your code above these URLs dont match.

#nahidf: I have updated the url from the react native and the ASP app. The log in still is fine on the android app, but after I log in, the app stays open with the home page of that ASP app and not closing to return to react app and also return the token. Does the expo app have to be published because I am running it locally? Also, the ASP I have published it on a local server.

Related

How to handle Auth with WebBrowser.openAuthSessionAsync in Expo?

I created a auth flow using WebBrowser.openAuthSessionAsync, the opening and the closing are working as expected but my problem comes with the return, I only receive back from the browser: {"type": "success", "url": "exp://192.168.0.11:19000/--/App"}, the type and my return URL.
I'm trying to figure out how to have access to any kind of info from the return, from cookies to query string on the URL as I have control over how my API handles the return, but I'm not being able to access any kind of info from the AuthSession.
Here is the implementation:
import { useEffect, useState } from "react"
import { Button, View, Text } from "react-native"
import * as WebBrowser from "expo-web-browser"
import * as Linking from "expo-linking"
export default () => {
const [result, setResult] =
useState<WebBrowser.WebBrowserAuthSessionResult | null>(null)
useEffect(() => {
console.log(result)
}, [result])
const _handlePressButtonAsync = async () => {
const baseUrl = "https://...com"
const callbackUrl = Linking.createURL("App", { scheme: "myapp" })
setResult(
await WebBrowser.openAuthSessionAsync(
`${baseUrl}/login?returnUrl=${encodeURIComponent(
`${baseUrl}/_v/login?token=...&iv=...&returnUrl=${callbackUrl}`
)}`,
callbackUrl
)
)
}
return (
<View className="items-center justify-center flex-1">
<Button title="Open Auth Session" onPress={_handlePressButtonAsync} />
{result && <Text>{JSON.stringify(result)}</Text>}
</View>
)
}
It's a SASS platform, that does not follow only the OAuth flow, which made me choose this method over https://docs.expo.dev/versions/latest/sdk/auth-session/

React native Expo error trying to sign in with Google federated in apple device

I am trying to implement google federated sign in, with AWS Amplify, in react native manged flow by expo, and a get a weird error only in my apple physical device.
I followed the documentation in <https://docs.amplify.aws/lib/auth/social/q/platform/js/#full-samples> and got this code:
import React, { useEffect, useState } from 'react';
import { Button, Linking, Platform, Text, View } from 'react-native';
import * as WebBrowser from 'expo-web-browser';
import Amplify, { Auth, Hub } from 'aws-amplify';
async function urlOpener(url, redirectUrl) {
const { type, url: newUrl } = await WebBrowser.openAuthSessionAsync(
url,
redirectUrl,
);
if (type === 'success' && Platform.OS === 'ios') {
WebBrowser.dismissBrowser();
return Linking.openURL(newUrl);
}
}
const [
localRedirectSignIn,
productionRedirectSignIn,
] = awsconfig.oauth.redirectSignIn.split(",");
const [
localRedirectSignOut,
productionRedirectSignOut,
] = awsconfig.oauth.redirectSignOut.split(",");
Amplify.configure({
. "aws_project_region": "us-east-1",
"aws_cognito_identity_pool_id": "us-east-1:27777e6d-857e-4187-84b6-85a17891d320",
"aws_cognito_region": "us-east-1",
"aws_user_pools_id": "us-east-1_MLaD0VGx7",
"aws_user_pools_web_client_id": "6kanfftqjuu7pgv7415or1hkl9",
"oauth": {
"domain": "velpaf3732cb1-f3732cb1-dev.auth.us-east-1.amazoncognito.com",
"scope": [
"aws.cognito.signin.user.admin",
"email",
"openid",
"phone",
"profile"
],
"redirectSignIn": "exp://127.0.0.1:19000/--/,
"redirectSignOut": "exp://127.0.0.1:19000/--/,
"responseType": "token"
},
"federationTarget": "COGNITO_USER_AND_IDENTITY_POOLS",
"aws_appsync_graphqlEndpoint": "https://tlpmf7idfvextlpbc4qrrmwgfy.appsync-api.us-east-1.amazonaws.com/graphql",
"aws_appsync_region": "us-east-1",
"aws_appsync_authenticationType": "API_KEY",
"aws_appsync_apiKey": "da2-3tpwvu3nobf35bk4mmx3m3gfnq",
"aws_user_files_s3_bucket": "velpanewversion3d014069f75d4be1bc2875498e9be3dc193449-staging",
"aws_user_files_s3_bucket_region": "us-east-1"
});
function App() {
const [user, setUser] = useState(null);
useEffect(() => {
Hub.listen('auth', ({ payload: { event, data } }) => {
switch (event) {
case 'signIn':
getUser().then((userData) => setUser(userData));
break;
case 'signOut':
setUser(null);
break;
case 'signIn_failure':
case 'cognitoHostedUI_failure':
console.log('Sign in failure', data);
break;
}
});
getUser().then((userData) => setUser(userData));
}, []);
function getUser() {
return Auth.currentAuthenticatedUser()
.then((userData) => userData)
.catch(() => console.log('Not signed in'));
}
return (
<View>
<Text>User: {user ? JSON.stringify(user.attributes) : 'None'}</Text>
{user ? (
<Button title="Sign Out" onPress={() => Auth.signOut()} />
) : (
<Button title="Federated Sign In" onPress={() => Auth.federatedSignIn()} />
)}
</View>
);
}
export default App;
This is my awsExport:
const awsmobile = {
"aws_project_region": "us-east-1",
"aws_cognito_identity_pool_id": "us-east-1:27777e6d-857e-4187-84b6-85a17891d320",
"aws_cognito_region": "us-east-1",
"aws_user_pools_id": "us-east-1_MLaD0VGx7",
"aws_user_pools_web_client_id": "6kanfftqjuu7pgv7415or1hkl9",
"oauth": {
"domain": "velpaf3732cb1-f3732cb1-dev.auth.us-east-1.amazoncognito.com",
"scope": [
"aws.cognito.signin.user.admin",
"email",
"openid",
"phone",
"profile"
],
"redirectSignIn": "exp://127.0.0.1:19000/--/,exp://exp.host/#mateodelat/velpa/",
"redirectSignOut": "exp://127.0.0.1:19000/--/,exp://exp.host/#mateodelat/velpa/",
"responseType": "token"
},
"federationTarget": "COGNITO_USER_AND_IDENTITY_POOLS",
"aws_appsync_graphqlEndpoint": "https://tlpmf7idfvextlpbc4qrrmwgfy.appsync-api.us-east-1.amazonaws.com/graphql",
"aws_appsync_region": "us-east-1",
"aws_appsync_authenticationType": "API_KEY",
"aws_appsync_apiKey": "da2-3tpwvu3nobf35bk4mmx3m3gfnq",
"aws_user_files_s3_bucket": "velpanewversion3d014069f75d4be1bc2875498e9be3dc193449-staging",
"aws_user_files_s3_bucket_region": "us-east-1"
};
export default awsmobile;
When I do the sign in with google from my virtual emulator device I can do a successful login.
Logging from my physical apple device, I can open the web browser but once authorized from google, get the following screen:
Image error

How can I authenticate Spotify in a React Native component?

I am creating a React Native mobile application for a school project and I want a user to be able to login to Spotify to be able to get information about their playback using the Spotify API.
I am developing for iOS and using Expo, so have found this documentation from Expo to be quite helpful. Using their sample Auth Code, I was successfully able to make a very simple React app that allows the user to push a button that prompts them to login with Spotify. For my project, though, I have different components corresponding to different screens in my app. Whenever I try to move the code into a component, I get an error that I am unsure how to resolve (I am still pretty new to React and Javascript).
Here is the code:
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { View, Button, StyleSheet } from 'react-native';
import { LoginContext } from '../LoginContext';
WebBrowser.maybeCompleteAuthSession();
// Endpoint
const discovery = {
authorizationEndpoint: 'https://accounts.spotify.com/authorize',
tokenEndpoint: 'https://accounts.spotify.com/api/token',
};
const [request, response, promptAsync] = useAuthRequest(
{
clientId: CLIENT_ID,
scopes: ['user-read-email', 'playlist-modify-public'],
// In order to follow the "Authorization Code Flow" to fetch token after authorizationEndpoint
// this must be set to false
usePKCE: false,
redirectUri: 'exp://localhost:19000/--/',
},
discovery
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
export default class Settings extends React.Component {
render () {
return (
<View style={{flex: 1, display: 'flex', justifyContent: 'center', alignItems: "center", alignContent: 'center'}}>
<Button
//disabled={!request}
title="Login to Spotify"
onPress={() => {
//console.log("Login attempt");
promptAsync();
}}
/>
</View>
);
}
}
I get an error message that states:
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app See https://reactjs.org/warnings/invalid-hook-call-warning.html for tips about how to debug and
fix this problem.
I looked at the link in the error message to see if I could debug myself, but I am having some trouble given that I am still pretty new to React. Any help would be greatly appreciated!
useAuthRequest and useEffect are what we call React hooks. They can be used in functional components only. In my opinion, you should first check the documentation of how to use hooks in React: https://reactjs.org/docs/hooks-intro.html
WebBrowser.maybeCompleteAuthSession();
// Endpoint
const discovery = {
authorizationEndpoint: 'https://accounts.spotify.com/authorize',
tokenEndpoint: 'https://accounts.spotify.com/api/token',
};
// This is the functional component
const Settings = () => {
const [request, response, promptAsync] = useAuthRequest({
clientId: CLIENT_ID,
scopes: ['user-read-email', 'playlist-modify-public'],
// In order to follow the "Authorization Code Flow" to fetch token after authorizationEndpoint
// this must be set to false
usePKCE: false,
redirectUri: 'exp://localhost:19000/--/',
}, discovery);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
return (
<View>...</View>
)
}

React Native - GoogleSignIn - refreshToken, idToken, accessTokenExpirationDate are all null

I am trying to use expo-google-sign-in in my project. I am able to login with GoogleSignIn.signInAsync() but in the object I get back as a result the refreshToken, idToken and accessTokenExpirationDate are all null. Does anybody have any idea what the problem can be? I use expo 4.2.1. I have ejected the project and run it with react-native run-android
Here is my code:
import React from 'react';
import { View, Button } from 'react-native';
import * as GoogleSignIn from 'expo-google-sign-in';
export default function App() {
return (
<View>
<Button title='sign in' onPress={async () => {
await GoogleSignIn.initAsync({
clientId: '<MY CLIENT ID>',
scopes: [
GoogleSignIn.SCOPES.OPEN_ID
],
});
const r = await GoogleSignIn.signInAsync();
console.log(JSON.stringify(r));
}}/>
</View>
);
}
And the result is something like this:
{
"type": "success",
"user": {
"uid": "8978924659847365874563486867",
"email": "john.bar#gmail.com",
"displayName": "John Bar",
"photoURL": "https://lh3.googleusercontent.com/a-/AjsgdyegdrR-_wluyiuBUUKDKWBUWYDKUWGHDjO-M",
"firstName": "John",
"lastName": "Bar",
"auth": {
"idToken": null,
"refreshToken": null,
"accessToken": "ya29.a0AfH6SMCh1K322dfdf34rff6_gGHW98-kIngxBUUTtfRZTugtwff4f4wf4f4wf4fPib9fc2JPXmg7ewfGx7peVtC4ffwfw4fwfw4fw4fBeFkqJydbWxcwiUb",
"accessTokenExpirationDate": null
},
"scopes": [
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email"
],
"serverAuthCode": null
}
}
What is also strange that the scopes field in the result doesn't contain openid.
These are my credentials in my google cloud console (the SHA1 fingerprint comes from debug.keystore).
The Consent setup
Thanks in advance
Try to use webClientId instead of clientId in initAsync It worked for me.
await GoogleSignIn.initAsync({
webClientId: '<MY CLIENT ID>',
});

Opentok react native not publishing

I'm trying to build an app with opentok-react-native library.
When the app is running, the publisher component becomes a black square and the subscriber component shows my camera video.
In the logs I can see that the stream is created, with a stream ID and apparently is working. But when I go to my Tokbox account I can't see any data on my dashboard:
Here is my current code: https://github.com/victor0402/opentok-demo
The important part is:
import React, {Component} from 'react';
import {View} from 'react-native';
import {OT, OTPublisher, OTSession, OTSubscriber} from "opentok-react-native";
export default class App extends Component {
//Publisher token
token = 'TOKEN HERE';
//Routed session ID
session = 'SESSION ID HERE';
apiKey = 'API KEY';
constructor(props) {
super(props);
this.state = {
streamProperties: {},
};
this.publisherProperties = {
publishVideo: true,
publishAudio: true,
cameraPosition: 'front'
};
this.publisherEventHandlers = {
streamCreated: event => {
console.log('publisherEventHandlers: streamCreated.... updating state');
const streamProperties = {
...this.state.streamProperties, [event.streamId]: {
subscribeToAudio: true,
subscribeToVideo: true,
style: {
width: 400,
height: 300,
},
}
};
this.setState({streamProperties});
},
streamDestroyed: event => {
console.log('Publisher stream destroyed!', event);
}
};
this.subscriberProperties = {
subscribeToAudio: true,
subscribeToVideo: true,
};
this.sessionEventHandlers = {
streamCreated: event => {
console.log('sessionEventHandlers : streamCreated');
},
streamDestroyed: event => {
console.log('Stream destroyed!!!!!!', event);
},
};
this.subscriberEventHandlers = {
error: (error) => {
console.log(`There was an error with the subscriber: ${error}`);
},
};
}
render() {
OT.enableLogs(true);
return (
<View>
<OTSession apiKey={this.apiKey} sessionId={this.session} token={this.token}
eventHandlers={this.sessionEventHandlers}>
<OTPublisher
properties={this.publisherProperties}
eventHandlers={this.publisherEventHandlers}
style={{ height: 100, width: 100 }}
/>
<OTSubscriber
properties={this.subscriberProperties}
eventHandlers={this.subscriberEventHandlers}
style={{height: 100, width: 100}}
streamProperties={this.state.streamProperties}
/>
</OTSession>
</View>
);
}
}
So, is there something wrong with my code?
How can I publish a vĂ­deo and get the results and recordings on my account dashboard?
TokBox Developer Evangelist here.
The usage metrics take about 24 hours to populate in the dashboard which is why you're not seeing them immediately.
I also reviewed the code you shared and it looks like your setting the streamProperties object in the streamCreated callback for OTPublisher. Please note that the streamCreated event for the publisher will only fire when your publisher starts to publish. Since you're using the streamProperties to set properties for the subscriber, you should be setting this data in the streamCreated event callback for OTSession because that's dispatched when a new stream (other than your own) is created in a session. Your code would then look something like this:
this.sessionEventHandlers = {
streamCreated: event => {
const streamProperties = {
...this.state.streamProperties, [event.streamId]: {
subscribeToAudio: true,
subscribeToVideo: true,
style: {
width: 400,
height: 300,
},
}
};
this.setState({streamProperties});
},
};
Lastly to check and see if everything is working correctly, I recommend using the OpenTok Playground Tool and connect with the same sessionId as the one in your React Native application.
Hope you are using updated vrsion
"opentok-react-native": "^0.17.2"