react-nativeoauthexpotokenfusionauth

Fusion Auth is not redirecting to the Expo App oauth callback route after authorization


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

enter image description here

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:
enter image description here

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?


Solution

  • 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:

    Application config in FusionAuth