I am writing an app in Expo. I am using Fusion Auth as a auth server, hosted locally.
I want to use OAuth auth flow with PKCE, app is sending a request to /authorize, then i am getting the code, auth server should redirect me to auth callback route, this auth callback route should send that code to token endpoint. However, fusion auth is not redirecting me from to auth callback route, but closes browser modal and keeps the login page active.
I am writing code with https://docs.expo.dev/versions/latest/sdk/auth-session/
this is my application Fusion Auth config
and this is my login page code in Expo app:
import * as AuthSession from 'expo-auth-session'
import { Button } from 'react-native';
export default function Login() {
const discovery = AuthSession.useAutoDiscovery("http://192.168.100.17:9011/.well-known/openid-configuration/a5673e50-cbcc-14ec-5ae3-bde9d60a1950");
const [request, response, promptAsync] = AuthSession.useAuthRequest({
clientId: process.env.FUSIONAUTH_CLIENT_ID,
scopes: ['openid', 'offline_access'],
redirectUri: AuthSession.makeRedirectUri({
path: 'oauth', // route in "app" folder in expo router
scheme: 'myapp', // in app.json scheme is "myapp" too
}),
}, discovery);
return <Button title="Login" onPress={() => promptAsync()} />
}
this is how my app currently behaves:
this is the request object result:
{
"clientId": "7ae2dde8-5710-4f6f-b159-693a394e8eee",
"clientSecret": undefined,
"codeChallenge": "iPLWzzLvGhYvaCRbwb6-aBLdiOE8jLPEdLWWP91Xc8Q",
"codeChallengeMethod": "S256",
"codeVerifier": "c6RBW8y9mmWrqK0TRXQud7lYlPCsYS8QUQp4LZkKFZVG8ovruNzgMsDPjdGKPPkWv6bsZQtpQtcHQ24Hq0HHBLfhX36aliDIlTMumLtJzwL8Q7UI3M6rPxzv4zYpSbcD",
"extraParams": {},
"prompt": undefined,
"redirectUri": "exp://192.168.100.17:8081/--/oauth",
"responseType": "code",
"scopes": ["openid", "offline_access"],
"state": "PJIpedf69M",
"url": "http://192.168.100.17:9011/oauth2/authorize?code_challenge=iPLWzzLvGhYvaCRbwb6-aBLdiOE8jLPEdLWWP91Xc8Q&code_challenge_method=S256&redirect_uri=exp%3A%2F%2F192.168.100.17%3A8081%2F--%2Foauth&client_id=7ae2dde8-5710-4f6f-b159-693a394e8eee&response_type=code&state=PJIpedf69M&scope=openid%20offline_access",
"usePKCE": true
}
this is response object:
{
"authentication": null,
"error": null,
"errorCode": null,
"params": {
"code": "NDyy5uMqvstnap3JIz6GWyYk1Q34ayOjPvyY3lIsayk",
"locale": "pl_PL",
"state": "PJIpedf69M",
"userState": "Authenticated"
},
"type": "success",
"url": "exp://192.168.100.17:8081/--/oauth?code=NDyy5uMqvstnap3JIz6GWyYk1Q34ayOjPvyY3lIsayk&locale=pl_PL&state=PJIpedf69M&userState=Authenticated"
}
Why fusion auth is not redirecting me to redirect url? Should I build app using prebuild
or something? Or create a development build and use scheme like myapp://oauth
? But then I should create an Apple Developer account for that?
First of all, I'm not an Expo expert, but I was able to make it work by using the same page for the redirect URI instead of /oauth
:
export default function Login() {
const [accessToken, setAccessToken] = useState(null);
const discovery = AuthSession.useAutoDiscovery(urlToFusionAuth);
const redirectUri = AuthSession.makeRedirectUri({
scheme: 'myapp', // I've removed `path: 'oauth'`
});
console.log(redirectUri); // Just to make sure I have the right one in FusionAuth
const [request, response, promptAsync] = AuthSession.useAuthRequest({
clientId: process.env.FUSIONAUTH_CLIENT_ID,
scopes: ['openid', 'offline_access'],
usePKCE: true, // You should use PKCE
redirectUri,
}, discovery);
// This is being called after we are redirected back to this page after initial login
useEffect(() => {
if (response) {
if (response.type === 'success') {
AuthSession.exchangeCodeAsync({
clientId: process.env.FUSIONAUTH_CLIENT_ID,
code: response.params.code,
extraParams: {
code_verifier: request.codeVerifier,
},
redirectUri,
}, discovery)
.then(setAccessToken)
.catch((error) => {
console.error(error);
});
return;
}
// You should handle errors here :)
console.error(response);
}
}, [response]);
// Conditionally rendering a "Log in" button or the retrieved access token
return (
{(accessToken)
? <Text>{JSON.stringify(accessToken)}</Text>
: <Button disabled={!request} title="Log in" onPress={() => promptAsync()} />}
);
}
In my example, I'm using PKCE instead of having to provide a Client Secret when exchanging the code for an access token, so here's my application settings: