Building an OpenID Connect flow for mobile

Cyrille Courtiere
Klaxit Tech Blog
Published in
5 min readJun 8, 2020

Finding precise guidelines on how to implement OpenID Connect for native mobile apps is a harsh journey. Most resources available don’t follow best practices and the other ones leave some important questions unanswered.

The goal here is not to reinvent the wheel, but to present a simple yet complete & robust implementation of OpenID Connect flow for mobile. It is based on the Authentication Code Flow, complies with the last official guidelines available RCF 8252 and IETF OAuth 2.0 Security Best Practices, and is fully compatible with the OpenID Connect Foundation Android SDK and OpenID Connect Foundation iOS SDK.

Choices made

Opinionated choices have been made for this implementation.

1. Use the Authorization Code Grant flow

Given the vulnerability of the Implicit Grant flow, the Authorization Code Grant flow is the one that should be used from now on.

2. Use the browser for authentication

Thinking about an authentication flow on mobile, it is quite intuitive to go with a native implementation. However, keeping the authentication part in the browser helps you achieve important goals :

  • security: by going through the browser and using the web authentication form of the Authorization server, you instantly benefit from security mechanisms they may already implements (ie : sandboxing, certificates validation, …).
  • user trust: you may think that a native implementation is always more efficient. However, from a user point of view, having to provide his third party credentials directly in your application seems like a potential security issue. How could he know that you are not going to steal his credentials along the way ?
  • maintainability: when authentication goes through the browser, the authentication flow is delegated to the Authorization server. So what happens if the authorization server wants to enforce new mechanisms (ie : 2-factors authentication, user consent, …) ? Nothing on your side. It will be completely transparent for your application.

3. Take a simple path to keep the client_secret secret

Any secret bundled with a mobile application shouldn’t be considered a secret. AppAuth and RCF 8252 — section-5.3.3 emphasize this and state that to ensure that the client_secret should be kept server-side. Perfect, let's keep it server side but storing this secret in a "proxy" ! But as soon as you do that, you realize that without a strong authentication between your client application and your server, your client_secret will be kept secret but anybody can impersonate your application through your proxy. So any attacker catching the OAuth authentication "code" will be able to impersonate your app through your proxy and getting the user "access_token" from it.

Proof Key for Code Exchange (PKCE) has been created to prevent this without requiring a strong authentication between the client and the server. Used in conjunction with a mechanism preventing MITM attacks between your application and your server, client_secret will be able to be kept securely server-side while preventing anybody to perform a user authentication on your behalf.

4. Do not support everything

Android App links and iOS Universal Links support is key to this implementation. They require Android >= 6.0 Marshmallow and iOS >= 9. Since both were released in 2015, this shouldn’t be too much of a problem by now.

Abstract Flow

OpenID Connect mobile flow

Detailed Flow

Authentication is done in the browser by calling the authorize endpoint of the authorization server. This endpoint is often GET /authorize but it could be something else, depending on the authorization server configuration.

GET <AUTHORIZATION_SERVER_URL>/authorize?
response_type=code
&scope=openid email profile
&client_id=<CLIENT_ID>
&redirect_uri=<REDIRECT_URI>
&code_challenge_method=S256
&code_challenge=<PKCE_CHALLENGE>

response_type As we are implementing the “Authorization Code Grant flow”, should always be code.

scope Define the OpenID Connect claims you are interested about, see Requesting Claims using Scope Values.

client_id The identifiers of your application for the authorization server.

redirect_uri A valid URL recognized by your mobile application as an app link. It will allow to redirect the user back to your application after authentication and to get the “code” back from the authentication process.

code_challenge_method PKCE code challenge method, S256 (SHA-256) MUST be used if the client supports it.

code_challenge PKCE code challenge, see PKCE Protocol to implement it yourself or use AppAuth to handle it for you.

Security concerns

  • This authentication process should be handled using a registered browser or an “in-app browser tabs”, not a “web-view” (see RFC 8252).
  • You should use Android app links and iOS universal links, so only your application is able to get the authorization callback through redirect_uri.
  • You may want to restrict which browser is allowed for this process, AppAuth provides a mechanism to control which browser is used for authorization.
  • If your authentication server does not support PKCE, the code_challenge and code_challenge_method parameters won't be supported. In this case, at least use the state parameter to prevent CSRF attacks (see https://tools.ietf.org/html/draft-ietf-oauth-security-topics-15#section-4.7.1).

2. Getting token & claims

We will now go through our application server. We define a POST /open_id_connect endpoint on our server for this purpose. This endpoint will exchange the given code (and code_verifier) with the access_token and the claims from the authorization server.

POST <APP_SERVER_URL>/open_id_connect?
code=<CODE>
&code_verifier=<CODE_VERIFIER>

code The code returned by the authorization server to the redirect_uri on successful user authentication.

code_verifier PKCE code_verifier that matches the code_challenge sent to the authorization server during authentication process.

Response

In our implementation we also add an account property that will provide info on the existing user account on the platform (if any).

Response (no account associated to these credentials)

{
"access_token": "<oauth_access_token>",
"claims": {
"sub": "<oauth_sub>",
"email": "<oauth_email>",
"given_name": "<oauth_first_name>",
"family_name": "<oauth_last_name>"
},
"account": null
}

Response (associated account already exits)

{
"access_token": "<oauth_access_token>",
"claims": {
"sub": "<oauth_sub>",
"email": "<oauth_email>",
"given_name": "<oauth_first_name>",
"family_name": "<oauth_last_name>"
},
"account": { ... }
}

Security concerns

(2 bis). Getting token & claims

Behind the scene, your app server will have to call the authentication server to gather info.

Calling the token endpoint

This call will allow your app server to exchange the code with an access_token.

curl -X POST '<AUTHORIZATION_SERVER_URL>/token' \
-H "Content-Type: application/x-www-form-urlencoded" \
-d 'grant_type=authorization_code' \
-d 'client_id=<CLIENT_ID>' \
-d 'client_secret=<CLIENT_SECRET>' \
-d 'redirect_uri=<REDIRECT_URI>' \
-d 'code=<CODE>' \
-d 'code_verifier=<PKCE_CODE_VERIFIER>'

The returned id_token must be validated according to OpenID Connect specification- section 3.1.3.7. If you are already validating the SSL/TLS certificate of the provider, only validations 2, 3 and 9 must be performed (iss, aud and exp claims validation).

Calling the userinfo endpoint

This call will allow your app server to get the “claims” requested during authentication (giving the same scope parameter).

curl -X POST '<AUTHORIZATION_SERVER_URL>/userinfo' \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-d 'client_id=<CLIENT_ID>' \
-d 'client_secret=<CLIENT_SECRET>' \
-d 'scope=openid profile email'

That’s it !

You now know the end-user identity from the authorization server ( access_token and claims) and if the user is already registered on your server ( account). So you can decide if the end-user need a "sign-up" or a "sign-in", and use the gathered info to support these processes.

Building a simple & secure OpenID Connect authentication flow on mobile is quite simple, if you follow the right guidelines ;-) Happy to answer any suggestions !

--

--