Authentication Service Configurations for your Javascript (SPA) clients and mobile applications (Native)

Gayan Fonseka
9 min readDec 30, 2019

One of the key components (Service) of microservices architecture is the authentication service that you may have to build. Getting it right is of paramount importance, as the security of the data relies on what you build. In my journey towards this, I used IdentityServer4 and build on top of it and would like to share some notes from the material I consumed, which are mostly taken from various speeches/presentations of Brock Allen and Dominick Baier.

The reason I am sharing this is, as a developer there is a high possibility of you requesting/expecting a login API to pass the username and password and finish the whole process, which should not be the case. Also if you are new to OAuth and OpenID Connect, you may not get these concepts assimilated with the tight project deadlines.

We implement security as a service known as security token service that knows about our users, clients, APIs and can be like the traffic cop between those services to ensure that the users can access only what they are allowed to access. For this we have protocols.

For authentication, that is to know the user in front of the machine, we use OpenID Connect protocol. OpenID Connect out sources this problem to the Security Token Service in other words the protocol defines how the client talks to the token service. When the user is known, the next challenge is how do you transmit the user to the next hop, to the API that we need to call, basically delegating the identity. Talking to the APIs on behalf of the user is done using OAuth 2.0. OpenID connect is basically an extension to OAuth 2.0. With this being the case now both authentication and getting a token can be done in one trip to the token service.

1 For Javascript(SPA) applications

Cookies are the typical approach for server-side applications (server-rendered) but not appropriate for modern JavaScript apps. SPA’s are doing the UI in the client-side and no server-side HTML framework. Cookies are also not recommended for APIs due to cross-site request forgery security issues. So we need something that could prove the user and we rely on tokens.

OpenID Connect — Modern security protocol designed for modern applications
Allows for authentication to client applications (authenticate the user) — with id_token
Allows for securing server APIs — with access_tokens

OpenId Provider (OP)
* Issues tokens
1) The client makes a request to OP
* User authenticates
* User consents (optional)
2) OP returns to clients
* Accept id token
* Client validates id token

1.1 User authentication and getting the tokens

We get the Id token in the form of a JSON web token

The client-side JavaScript application to trust the token, it has to validate the token by going through the following steps,

· Verify state is same as sent in request (prevents XSRF /replay)

· Base64Url decode id_token and parse into JSON (formatting step)

· Verify nonce is same as sent in request (prevents XSRF/ replay)

· Validate signature on token (establishes trust [requires crypto])

· Validate iss same as issuer of OIDC OP (establishes trust)

· Validate aud same as this client’s identifier (prevents privilege escalation)

· Validate exp is still valid (prevents stale tokens)

OidcClient
This entire process is coded and available at the following location
https://github.com/IdentityModel/oidc-client-js

You may have the filter_protocol_claims: true to ensure that we get only the data relevant to us, which means the user data of the authenticated user.

The information we get back after validating the user are as follows,

sub: unique identifier of the user.
The unique identifier may not be very useful, instead I may need first name, last name and etc. We could get this from the Scope parameter.

Scope: openid profile email roles api1

If you don’t want to pack all this information to the token, then you could talk to the userinfo_endpoint . The id_token being too long to fit into a URL is a reason for the use of this. Using the endpoint requires authorization with an access token obtained in the OIDC request.

To get both the id_token and the access token configure the response_type as,
response_type : “id_token token
The config marked in italic requests for the access token.

Have load_user_profile : true if you need the entire user data to be available after the token validation process.

1.2 Accessing resources (APIs) using tokens

The recommended method is to use the implicit flow. The scope models a resource hence add more scopes if you need access to them as well.

Just release the tokens and you are good to some extent. You may also talk to the openid provider and call the end session.

The library will also handle token management. In token management, you need to consider token storage which could be local storage (across all tabs) as well as session storage (per tab). Also, you need to manage token expiration which may include waiting for 401 form API and then renewing it.

Get the next token via a hidden iframe. Use silent_renew : true to get the token renewed silently.

2 For mobile applications/native

The big picture is that there may be a bunch of APIs, web applications, browser-based applications, native applications where a human is holding a device with the application and server to server applications. In this, we focus on Native applications.

2.1 Not so right method

Why isn’t it (OAuth 2 resource owner password flow) so right? You are educating users to type in their password to any application. (When you wouldn’t enter your google password to any application). When both the front-end application and backend is developed by you, you might think it is ok, but things could change and there could be other third-party applications built to connect to your applications and the user will have to give his master key to others.

· Another concern is that you will have to login multiple times to different applications with the same username, password combinations. No cross application single sign-on or shared logon sessions.

· This approach would not support to add external authentication such as google, Facebook, twitter authentication support. No federation with external identity providers/ business partners.

· Also, every change in logon workflow will require versioning the application.

2.2 The right approach

The recommended and much more flexible approach is to NOT render the login dialog natively in the application (on the device) but to render in a web server and show that web page inside the application known as the web view.

· Now you get a unified login experience across applications, even if they are third-party applications.
· Implement once and all applications get it
· Allows changing the flow without changing the application
· Can easily integrate external identity providers as they are any way browser-based
· Authentication sessions could be shared based on browser

The types of browsers that could be used

· Embedded web view
· Authentication broker — special browser E.g. WebAuthenticationBroker
· In-app browser tab — a full blown browser — the best and most recommended

Now that you have decided to go with the browser based approach there are two protocol flavors that you might want to consider,
Implicit flow
· Really designed for JS apps
· Access tokens transmitted over browser (and potentially cross process)
· No refresh tokens

Authorization code-based flows
· Access tokens only over back-channel communication
· Slightly more secure due to client secret
· Allows long lived API access via refresh tokens
· Authorization code itself needs to be protected through
— -Cut and paste attacks
— -Man in the middle

The recommended method is to use the authorization code flow.

2.3 The flow with authorization code

The flow mentioned in the specification,

The above figure illustrates the interaction of the native app with the system browser to authorize the user via an external user-agent.

1) The client app opens a system browser with the authorization request (e.g. https://idp.example.com/oauth2/auth...)

2) Server authenticates the end-user, potentially chaining to another authentication system, and issues Authorization Code Grant on success

3) Browser switches focus back to the client app using a URI with a custom scheme or claimed HTTPS URL, passing the code as a URI parameter.

4) Client presents the OAuth 2.0 authorization code and PKCE [RFC7636] proof of possession verifier.

5) Server issues the tokens requested.

Practically when the user clicks on the login button the browser opens with a start url that has the login page. The redirect_uri (last) would take the user back to the native application once the authentication is done.

The redirect_uri looks strange and that is done to prove that the uri belongs to you. Then you do something known as “reserve the dns scheme”. Next you put the name of the app, which in this case is “nativeapp”. This is done to avoid accidental collision.

In the above example we are using the hybrid flow, and that is why you see response_type = code id_token . Hybrid means we do one part of the work via browser front channel, and one part of the work via back channel communication. What we get via the front channel is the id_token that is the signed response token that we get back and an authorization code which we will use afterwards. You can do Authorization code flow only with response_type = code and the benefit of having the id_token is you get back a token that you can validate. Having the id_token helps prevent cut’n paste attack. The code_challenge helps in preventing the malicious change of redirect_uri. The nonce is used to prevent replay attacks.

As the response you get the id_token and the authorization code.

After doing the necessary validations, we send the code to get the actual access token.

In the second leg(web API communication) we send the code verifier which is the un-hashed version. Token service takes the code verifier and using the same hashing algorithm, makes sure that this matches the code_challenge sent in the first leg. This makes sure that the client application in the second leg is the same as the one in the first leg. The reason being the potential for man in the middle. The man in the middle would not know the code_verifier. It is there to creating a per-request secret between client application and token service.

The last leg which is optional is to download the claims of the user (name , profile picture etc). We do not put this into the token itself to keep it small. You can use the userinfo end-point to get that information.

Then you need to persist the data in protected storage,
* Claims
* Access token
* Refresh token

References
Carrying out the above steps is a lot of work. Native libraries have been built for this purpose and could be found in the following locations,
Implementations
https://github.com/openid/AppAuth-iOS
https://github.com/openid/AppAuth-Android
https://github.com/IdentityModel/IdentityModel.OidcClient2

Protocol doc
https://tools.ietf.org/html/draft-wdenniss-oauth-native-apps-02

--

--