Search code examples
react-nativeazure-appserviceapple-sign-ineasy-auth

How do I refresh my Apple login in my React Native app without user prompt, and use the result to refresh login to my Azure App Service?


I have a React Native app that communicates with a backend API on Azure. This is the setup:

  1. A React Native app, developed with and built on Expo.
  2. An API (FastAPI, but that's irrelevant) deployed in a container in Azure App Service
  3. Apple Sign in in my app, using expo-apple-authentication
  4. EasyAuth enabled on the App Service, with Apple as an identity provider, set up according to this documentation

The Apple sign in code is the same as the example:

const credential = await AppleAuthentication.signInAsync(
   {
      requestedScopes: [
         AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
         AppleAuthentication.AppleAuthenticationScope.EMAIL,
      ],
   }
);

And so far that all works, using the provider SDK flow described by the Azure docs:

  1. The user touches the Sign In button
  2. The user authenticates, on my phone conveniently with Touch ID
  3. The app gets the identityToken from the credentials
  4. The app sends a POST request to myapi.azurewebsites.net/.auth/login/apple, with {id_token: identityToken}
  5. The API (or rather, the App Service) returns a JWT
  6. I use the API with the JWT provided in the X-ZUMO-AUTH header.

However, both the identity token and the JWT expire after a day, which means the user needs to log in again. How can I refresh my tokens, without the user having to sign in again?

Solutions I tried (that didn't work)

Using the identity provider

The Expo docs mention AppleAuthentication.refreshAsync as a way to refresh the token.

However:

  1. It always prompts the user, so what's the point?
  2. In my case, it returned something rather useless like {'_i': 0, '_j': 0, '_k': 0, '_l': 0}. I can't remember the exact result, but there was zero information.

Using the app service

The Azure docs are very brief on refreshing tokens, it simply says "send a GET request to /.auth/refresh". Now, with cookies in a browser that might indeed work, but from an app, we need a bit more. What I tried:

  1. GET https://myapi.azurewebsites.net/.auth/refresh -H "Authorization: Bearer **apple id token**", returned 401
  2. GET https://myapi.azurewebsites.net/.auth/refresh -H "Authorization: Bearer **Azure JWT**", returned 401
  3. GET https://myapi.azurewebsites.net/.auth/refresh -H "X-ZUMO-AUTH; **Azure JWT**", returned 403
  4. POST https://myapi.azurewebsites.net/.auth/refresh -H "Content-Type: application/json" -d '{"id_token": **apple id token**}', returned 401

For now, I simply require the user to login after a day (after the tokens expired), but I don't like that UX. It feels like this shouldn't be rocket science, and I'm missing something simple. What's a better way? Who managed this, and how?


Solution

  • Okay, I figured it out. Microsoft seems confused, but as per this issue, Azure App Service does not (yet) support storing and managing Apple refresh tokens. You need to implement this on your own. Fortunately, this is straightforward. The process is this:

    1. Authenticate to Apple on the device with expo-apple-authentication. The signin method returns an authorizationCode.
    2. Send this authorization code to a custom backend endpoint. Do this immediately, this code is valid for 5 minutes.
    3. From this backend endpoint, send a request to https://appleid.apple.com/auth/token with the authorization code and your client secret. Apple returns a refresh token and an identity token. Return these to the client app.
    4. On the app, store the refresh token in persistent storage, and keep the identity token somewhere in memory. It expires after 24 hours.
    5. Follow the normal auth flow for Azure App Service, with a request first to https://myapi.azurewebsites.net/.auth/login/apple to get the ZUMO JWT, and provide this JWT in subsequent requests to your server.

    On refresh:

    1. On the app, load the refresh token from persistent storage.
    2. Send the refresh token to another custom backend endpoint.
    3. From this backend endpoint, send a request to https://appleid.apple.com/auth/token with the refresh token and your client secret. Apple returns an identity token. Return this to the client app.
    4. Follow the normal auth flow for Azure App Service, with a request first to https://myapi.azurewebsites.net/.auth/login/apple to get the ZUMO JWT, and provide this JWT in subsequent requests to your server.

    This link provides details on how to implement the refresh and refresh token requests.

    Note that you do not need a backend endpoint per se, but you have to store your Apple client secret somewhere securely, and this secret needs to be refreshed at least every 6 months.