Authentication in SPA (ReactJS and VueJS) the right way - Part 2

OAuth2, Saml, OpenID Connect, SSO, Grant flow, everything you need to know

Avatar of Author
Jean-Christophe BaeyOctober 18, 2019
Featured image
Photo by Chris Barbalis on Unsplash

TL;DR;

When you create a web application or a SaaS service, you want to restrict access to registered users only. You need to authenticate your users; in other words, you want to know who is using the application: authentication, and you need to know whether they have the right to do the requests: authorization. Authorization means deciding which resources a certain user should be able to access, and what they should be allowed to do with those resources.

Before going further into the implementation in a ReactJS or VueJS SPA (Single Page Application) apps, we will go through protocols that are involved nowadays in authentication and authorization.

You have 2 options to identify your users

  • local user authentication: you have your own user pool. You create a sign-up form to ask for login (email) and a password, and you store those credentials in your database (passwords must not be stored in plain text, you will have to hash it using for instance bcrypt).

    Local authentication can be bad for business: people find sign up and account creation tedious, and users may abandon apps or shopping carts. For enterprises with many apps, the maintenance of separate user databases can quickly become an administrative and security nightmare.

  • The other option is to delegate user authentication and permission to a third party service that provides Identity Provider (IdP), for instance, Google, Facebook, Amazon. If those third party services are shared among different applications, you can allow a user to enter this username and password once and get access to multiple applications. This idea is called single sign-on (SSO).

We will focus on the IdP option!

Identity Provider standards

There are two dominant open web standards for online identity:

  • SAML
  • OpenID Connect (also called OIDC) that runs on top of OAuth 2.0 protocol.

IdP

OpenID Connect, published in 2014, is not the first standard for IdP, but definitely the best in terms of usability and simplicity, having learned the lessons from past efforts(SAML and oldest version of OpenID).

SAML (Security Assertion Markup Language)

SAML is an open standard, based on XML-based protocol messages that provides both authentication and authorization. SAML defines a principal, which is the end-user trying to access a resource. There is a service provider (SP), which is the webserver that the principal is trying to access, and there is an identity provider (IdP), which is the server that holds the principal’s identities and credentials. The service provider requests and obtains an authentication assertion from the identity provider. On the basis of this assertion, the service provider can decide whether to perform the service for the connected principal. SAML does not specify the method of authentication at the identity provider.

SAML is/was very common in corporate environments as it is the oldest standard. It was originally developed in 2001, and its latest major update was in 2005.

Basically if you are new in IdP and you don't have to manage legacy stuff, you can ignore SAML.

OpenId Connect (OIDC)

OpenID Connect is close to Google’s authentication API. OpenID Connect standardizes the flow for person authentication using OAuth2. Previously we had too many proprietary API’s that did the same thing. OpenID Connect represents years of work to align consumer IDPs (like Microsoft, Google, Yahoo…) and other industry participants on a single profile of OAuth 2.0 for authentication. OpenID Connect (OIDC) was created in early 2014, and it is promoted by the non-profit OpenID Foundation.

We need to define first what is OAuth 2.0 before digging into OpenId connect.

OAuth 2 defines the flows to authorize access to a resource, whereas OIDC defines and normalizes the content of the messages involved in those flows. It also uses the messages to carry out the identity of the user.

OAuth 2.0

OAuth 2.0 is an authorization framework, not an authentication protocol. OAuth2 is generic so that it could be applied to many authorization requirements (API access management, IOT services, ...).

OAuth2 provides secure delegated access, meaning that an application, called a client, can take actions or access resources on a resource server ** on behalf of a user, without the user sharing their credentials with the application**.

For instance, you can grant access to your twitter account to an application without reveling your twitter password.

OAuth2 does this by allowing tokens to be issued by an identity provider to these third-party applications, with the approval of the user. The client then uses the token to access the resource server on behalf of the user. We talk about access token. Those tokens typically have a limited lifetime.

OAuth 2.0 terminology

OAuth 2 entities
OAuth 2.0 terminology
  • Resource Owner: the entity that can grant access to a protected resource. Typically this is the end-user.
  • Client: an application requesting access to a protected resource on behalf of the Resource Owner. A client is registered on the IdP and gets a client_id and a client secret;
  • Resource Server: the server hosting the protected resources. This is the API you want to access.
  • Authorization Server: the server that authenticates the Resource Owner and issues Access Tokens after getting proper authorization (the IdP).
  • User-Agent: the agent used by the Resource Owner to interact with the Client, for example, a browser or a native application.

Grant flows

OAuth 2.0 supports different flows also called grants.

Grants are ways of retrieving an Access Token.

Deciding which one is suited for your use case depends mostly on your client's type, the trust for the client (web app, mobile/native app, server), or the experience you want your users to have.

OAuth 2.0 defines 4 major grant flows to delegate access on behalf of a user:

  • Authorization Code Grant: the client gets a temporary authorization code once the user grants the access, and then it exchanges the code for an access token (2 steps).
  • Implicit Grant: the client gets an access token once the user grants the access (1 step).
  • Password: the client gets an access token in exchange for the user credentials.
  • Refresh Token: used by clients to exchange a refresh token for an access token when the access token has expired.

And two additional flows for specific use cases:

  • Client Credentials Flow (for M2M use cases): An application (a command line, for instance) gets an access token in exchange for a client_id and a secret.
  • Device Authorization Grant for devices with no browser or limited input capability to obtain an access token. This is commonly seen on Apple TV apps.

I recommend to understand at least the 2 followings:

Authorization Code Grant

Authorization Code is used when you can protect/secure a secret. For instance, you have a backend server (like in a regular PHP web-app) that is hosted in a secured place.

The secret must NOT be shipped into the application source code or binary that is deployed on the user browser, mobile, or desktop: security through obscurity is not valid at all.

The Implicit grant type is a simplified flow that can be used by public clients, where the access token is returned immediately without an extra authorization code exchange step.

Implicit Grant

Implicit grant is used when you can't protect a secret: SPA application, Mobile application, etc...

We will discuss those two flows in the security chapter.

OAuth 2.0 and OIDC?

Back to OIDC: OAuth2 is the basis for OpenID Connect, which provides OpenID (authentication) on top of OAuth2 (authorization) for a complete security solution.

OpenID Connect is a profile of OAuth 2.0 that defines workflows for authentication. The big difference between OpenID Connect and OAuth2 is the id_token. There is no id_token defined in the OAuth2 protocol because the id_token is specific to authentication.

OIDC uses simple JSON Web Tokens (JWT) for the token format, which you can be obtained using flows conforming to the OAuth 2.0 specifications.

OIDC purpose is to give you one login for multiple app/websites. Each time you need to log in to a website using OIDC, you are redirected to your OpenID site where you log in, and then taken back to the website.

For example, once you successfully authenticate with Google and authorize a client app to access your information, Google will send back to the client app information about the user and the authentication performed. This information is returned in a JWT format. The client app will receive an Access Token and, if requested, an ID Token that contains the user identity in a JWT payload.

The benefits of JWT are consequent:

  • the payload of the token is signed using asymmetric keys: the IdP signs the payload with its private key that remains secret and provides the public key or a list of public keys that can be used to verify the signature.
  • JWT is a standard, and a lot of libraries can handle JWT

OpenID Connect defines

  • Standard claims for authentication, i.e. properties of the user profile, for instance name, family_name, given_name, middle_name, ...
  • OAuth scopes to retrieve the authentication claims: for instance 'email', 'openid', 'profile'
  • Endpoints in an OIDC discovery documents : https://YOUR_DOMAIN/.well-known/openid-configuration

How to use OIDC/OAuth 2 in a SPA?

In your SPA, only 2 flows can be used:

  • Implicit grant (not recommended)
  • Authorization code (using a browser and server / using a browser only with PKCE)

The implicit grant is generally not recommended. The flow was written in the time where browser javascript was unable to perform cross-domain requests. The implicit grant relies only on GET requests and browser redirection with the usage of the URL fragment (https://YOUR_DOMAIN/#access_token) It is now discouraged from using it in the RFC.

The implicit grant has the following flaws:

  • no mechanism to return a refresh token in the Implicit flow
  • vulnerable to access token leakage: in the browser history, in the website referrer
  • vulnerable to access token replay and fishing

We will go deeper into security flaws in the next section.

Today, Cross-Origin Resource Sharing (CORS) is universally adopted by browsers, removing the need for this compromise. CORS provides a way for JavaScript to make requests to servers on a different domain as long as the destination allows it. This opens up the possibility of using the Authorization Code flow in JavaScript on browser-side with the PKCE extension instead.

Authorization code in a SPA with a backend server

Here is an implementation proposal to do an authorization code grant in a Single Page Application. The JWT is stored in a cookie as recommended in my previous article.

Authorization code Grant

The main advantages are:

  • The SPA contains very few lines of code concerning the authentication: only a fallback that makes a redirection to your backend in the case of a 401 error.
  • It reduces the coupling with your authentication provider.

Authorization code in a SPA on browser-side only

The authorization flow can now be done on the browser only thanks to the PKCE extension. It is then recommended if you can access a backend server without a valid token.

Security aspects

In addition to what has been discussed in my previous article, the authentication flows increase the attack surface. I will try to list the potential attack and the corresponding mitigation. Full list available here.

Access token credentials (as well as any confidential access token attributes) MUST be kept confidential in transit and storage, and only shared among the authorization server, the resource servers the access token is valid for, and the client to whom the access token is issued. Access token credentials MUST only be transmitted using TLS (httpS).

The signature and the expiration date of every token should always be verified. The OIDC discovery documents contain the signature algorithm and the public key to use to verify the signature.

Token leakage

The goal for the attacker is to steal the token and reuse it to impersonate you. It can be done thanks to

  • XSS: Cross Site Scripting: the attacker may compromise a web site JS dependencies or use user input to add malicious javascript code to steal a victim’s JWT.
  • token that is printed in server log if TLS is not used
  • Referrer: when a user clicks on a link (a), the browser sends the origin in the referrer header so that the destination website can do some analytics. If the click happens right after getting the token as a URL fragment (hash), the referrer may contain the access token.
  • browser history: the browser history may contains the access token if the token is passed to the website through a query param or a fragment (hash)

Mitigation

  • XSS attacks can be mitigated by escaping and controlling user-generated content, but it will be very difficult to detect and mitigate a compromised web dependency served by a public CDN.
  • Make sure you built a secure JavaScript application (knowing exactly which third-party libraries you’re using in your application)
  • Strong CSP: Content Security Policy can prevent XSS and empty the referrer.

Token reuse/replay

An attacker who owns a site A, can try to reuse user token on another site B that use the same IdP. For instance, the attacker has a web site A that uses Facebook Connect to authenticate and grant access to his website. The attacker can try to get the user tokens (from the server that he owns) and reuse them on a targeted website B that also use Facebook Connect and has weak audience verification.

In technical words, a JWT token issued by a public IdP for a site A could be reused for a site B that uses the same IdP and has weak issuer and/or audience verification.

Mitigation

The JWT payload defined in OIDC has 2 important text fields:

  • iss: the issuer
  • aud: the audience

The issuer contains the URL of the IdP used to generate the token. It should be compared to a white list.
The audience is the destination of the token. It should be compared to a white list.

If the issuer or audience doesn't match the white list, an unauthorized exception should be throw.

Phishing

The goal here for an attacker is to send an email to a victim with a specific link.

Let say you own a website A that uses authentication code grant flow to authenticate your users. Your website is a SaaS service that bills monthly.

An attacker can try the following:

  • He sends an email to your users that claims that the user’s credit card is about to expire with instructions are given on a link.
  • The link injects an authorization code (the attacker code) so that when the user clicks the link, he is put in the middle of the authorization flow,

the server will exchange the given code for an access token. The access token will contain the attacker's identity.

  • The victim will be duped and may think he is connected on his own profile, which is not the case.
  • The victim will enter his credit card details on the attacker account.

The same attack can be performed with both Authorization code or access-token if the implicit grant flow is used.

Phishing and token replay can be used at the same time; for instance, a malicious website can use an iFrame to change the currently connected user silently.

The attacker may take advantage of an open URI redirection configuration on the IdP to redirect the user browser into a malicious website that can steal the user access token.

Mitigation

  • Do not code open URI redirection in your application: Client MUST NOT expose URLs, which could be utilized as an open redirector. Attackers may use an open redirector to produce URLs, which appear to point to the client
  • Make sure that your SPA application does not accept tokens that have not been issued on its request:
    • Using PKCE: Proof Key for Code Exchange
    • Using the state field in both Implicit and Authorization code flows: a state is a string parameter that can be passed to the IdP to get the authorization code. When the code is exchanged for a token, the IdP returns the token AND the state. The idea is the following: the SPA generates a random number and stores this number in the browser's local storage. It passes this number in the state when it starts the grant flow and will compare the returned state by IdP. If it doesn't match, it doesn't accept the token.

Wrap up

This article contains a lot of information about concepts and security concerns for your SPA. I recommend the Authentication code grant flow with a server or in the browser only with PKCE. You need to understand the core concept to make sure you don't leak the token, and you secure your app properly against CSRF and phishing, especially when you use a public/shared identity provider IdP (CSP, white-list issuer, white-list audience, no pattern in redirection URI, random state to control the flow initiator).

Feel free to discuss on twitter.

In case you missed the first part:

👉 Don't forget to follow me on Twitter to be notified when new posts are available!