OAuth and PKCE with React Native
OAuth is an authorization protocol that utilizes a third party to gain access to user information without exposing the user’s password. The OAuth website describes the process with a great analogy:
Many luxury cars today come with a valet key. It is a special key you give the parking attendant and unlike your regular key, will not allow the car to drive more than a mile or two. Some valet keys will not open the trunk, while others will block access to your onboard cell phone address book. Regardless of what restrictions the valet key imposes, the idea is very clever. You give someone limited access to your car with a special key, while using your regular key to unlock everything.
Our new React Native library, react-native-app-auth, allows you to securely communicate with OAuth 2.0 and OpenID Connect. It bridges existing native authentication implementations for iOS and Android by OpenID and benefits from the same security enhancements.
But why did we bother? What’s so tricky about OAuth on mobile devices? The rest of this article will give you a brief overview of OAuth and the additional complications it brings to mobile devices.
The difference between OAuth and OpenID
The lines seem blurred between OAuth and OpenID, as they are frequently used together and have some common authors. To clarify: OpenID allows you to use an existing account to sign in to multiple websites without needing to create new passwords. Anyone can register to be an OpenID provider. This is how the “log in with Google/Facebook/Github” buttons have come about. These companies have registered to be OpenID providers and allow other websites to authenticate through them. OAuth, on the other hand, is an authorization protocol that dictates the exact API of the authorization server, how and when tokens get exchanged, and what security measures need to be taken to mitigate risks of malicious agents hijacking tokens.
OAuth: the 2 minute overview
Imagine you want to log into Medium using your Google account. We know that Google is an OpenID provider that implements the OAuth2 spec. How does that workflow work? Our example has 4 characters:
- Client (Medium) - the third party application that would like access to a user’s account. It needs permission in order to do so
- The Resource Server (Google API) - the API server used to access the user’s information
- The Authorization Server (Google UI) - the server that presents the interface where the user approves or denies the request
- The Resource Owner (you) - the person that is giving access to some portion of their account
Step 0 (only done once): a developer registers the app with the service
A Medium developer would have had to register the application on Google. That would include registering basic information such as application name, website, a logo, etc. In addition, they must register a redirect URI to be used for redirecting users.
Step 1: the user clicks on the “log in with Google” button
The user will be redirected to a Google-hosted page and prompted to log in. The request includes:
- Client ID - received from Google after setting up Step 0
- Redirect URI - the URI back to Medium
- Scope - how much of the user’s information the website would like, e.g. email
Step 2: The user is redirected back to Medium
After a successful authentication, the user is redirected back to Medium using the code.
Step 3: The token is requested using the auth code
Once the client had verified that the code received is authentic, it can make the request to exchange the auth code for the auth token.
Step 4: The auth server returns an access token
The auth server will verify this request and return the access token as well as a refresh token and expiry date when applicable.
Native Apps and PKCE
The second step of our authentication flow above involves linking back to our application with an auth code. This is a problem for native applications because the nature of how they are distributed through public stores prevents individual instances of applications from having unique (or secret) credentials. That means that it is impossible to guarantee that linking back into a native app will be caught by the application intended. Android prompts the user to choose between multiple apps claiming the same scheme, but iOS does not. If a malicious application gets itself registered as a handler of those URLs, they could intercept the handoff of the authorization code.
Since we cannot guarantee that the auth code is received by the correct application, the security problem needs to be handled elsewhere. PKCE to the rescue! PKCE (pronounced Pixie, believe it or not), or Proof Key for Code Exchange, is a spec that is designed to mitigate exactly this risk by preventing the malicious application, having already gotten hold of the auth token, from exchanging it for a more substantial token.
This works exactly as the auth flow described above, but with an additional parameter added to certain messages. When the native application first loads the login page in the browser, it also generates a code_verifier
string and passes that as a parameter on the URL. The Authorization Server stores away this string before returning the code back to the native application. When the native application then exchanges the code for the access token, it will include the code_verifier
string on that call. If the code_verifier
is missing or doesn’t match the previously recorded one, the Authorization Server will not return the access token. So, even if a malicious application is able to obtain a code, without the corresponding code_verifier
it will be unable to turn that code into an access token, and thus unable to access the business or personal data accessed through the APIs.
OAuth2 and React Native
Since a built React Native application is indistinguishable from a native app, it is also challenged by the same security vulnerabilities. OpenID has built tools to work with OAuth2 on the web, on Android and iOS. However, the existing JavaScript module was incompatible with React Native, which motivated us to build our own library - a React Native wrapper, bridging the native iOS and Android libraries - to allow you to easily and securely communicate using OAuth2 spec in React Native.