Adding OAuth to a React Native app

Every so often I'll want to add OAuth to an application. I'll spend some time re-reading best practices and documentation of different OAuth providers, but inevitably stop because I'm confused and questioning whether I know anything at all. In short - OAuth can be confusing. Thankfully I've had some a-ha moments recently and I hope that sharing these will make life easier for other React Native devs in the future as well. This example will run through adding Spotify's OAuth to a React Native application.

While I won't dive into the particulars of OAuth (there are plenty of resources explaining it far better than I could), this article was helpful in outlining how native applications should be handling OAuth.

Install React Native App Auth

One of the first packages we'll use in this example is react-native-app-auth. This takes care of the nitty gritty implementation details of properly handling OAuth urls in React Native - long story short, you don't want to perform OAuth in a typical webview, and there are several steps along the way that are beyond my understanding that this package will take care of - it's great.

If you briefly read through the docs of this package, you'll notice that it provides a bunch of functions that let you obtain an access token from a generic OAuth service. In order for everything to work properly, you'll need to pass a configuration object into the authorize, refresh, revoke, and register functions provided, and if you've registered the correct urls and are using the right tokens, everything should work great.

Create App on Spotify

After installing the package in your React Native application, you'll want to head over to the Spotify developer portal and register a new application. Creating a new app will provide the client id and secrets that we need to make calls on the users behalf.

React Native App Auth conveniently provides a sample configuration for Spotify:

Spotify OAuth Configuration w/ React Native App Auth

Now all we have to do is connect the dots. Provided that the configuration you've setup for react-native-app-auth matches that which you setup on Spotify, your authorize calls should work and you'll receive an access token that can be used with Spotify's backend. This is fundamentally all we wanted to do, so we should be done.

Managing Client Secrets

But wait -- reading a little bit further down in the react-native-app-auth README, you'll notice a clause at the bottom regarding security:

Managing Client Secrets in React Native

And it just so happens that our sample configuration includes a client secret. In fact, lots of configurations require a secret of some kind. They strongly recommend performing the code exchange step on your backend - so let's do that - but what does that mean?

This was the source of so much confusion for me for a long time - if you read the article regarding best practices, it clearly states that the OAuth token should be performed on the client, where the exchange for an auth token happens in the client's browser. But our configuration requires us to use a secret, which needs to be stored on the client, which we shouldn't do.

So what are we supposed to do?

Authorization Flow for Native Apps Using the Browser

This is the first a-ha moment that I had. The key here is that OAuth is performed in two distinct steps, one step to get an authorization code, and the other step to exchange it for an access token.

The fundamental misunderstanding on my part was not realizing that getting the auth token and getting the access token are two different steps, and more importantly it is this second step that requires the client secret - so we want to move this step to a token exchange service, which is a backend that will make this call for us.

Using a Token Exchange Service

It just so happens that the Spotify documentation for OAuth also provides similar warnings that are found in pretty much every article regarding OAuth in native applications - never store secrets on the client in a production application, and secrets should be moved to a backend call.

They also include a token exchange server that can be deployed to Heroku with one click of a button, it's super simple to set up and will get that client secret out of our app code and into a secure function. I recommend that you run this server locally first, and deploying it whereever you see fit.

Removing Secrets from the Client

Now that we've got the token exchange service up and running, we need to update our configuration:

//  Remove client secret, add our service as the token endpoint for OAuth

const config = {
clientId: "<client_id>", // available on the app page
redirectUrl: "com.myapp:/oauth", // the redirect you defined after creating the app
scopes: ["user-read-email", "playlist-modify-public", "user-read-private"], // the scopes you need to access
serviceConfiguration: {
authorizationEndpoint: "",
tokenEndpoint: "http://localhost:9292/api/token" // token exchange endpoint

Note that we updated the tokenEndpoint to point to our token exchange service, which will successfully return our acess token and remove the secrets from our app code.

That's it! Our authorize call should work just as it did before, and we've securely implemented OAuth in our app.