Search code examples
restauthenticationoauth-2.0single-page-applicationopenid-connect

How to perform user registration and authentication between a single page application and a REST API with OpenID Connect


Consider that we have:

  1. An SPA or a statically generated JAMStack website.
  2. A REST API.

The website is being served with nignx that also reverse proxies to our API.

--

It is required that a user should be able to register/authenticate with an identity provider (say, Google) through the OpenID Connect protocol. For the sake of simplicity, let us assume that the user has already registered with our API.

Talking about authentication using OIDC, from what I have read on the subject, the steps you take are the following:

  1. Register the application with the IdP and receive a client id and a secret.
  2. When the user initiates a login (with Google) request on the API ('/api/loginWithGoogle') the API sets a state variable on the request session (to prevent CSRF) and redirects the user-agent to the IdP's login page.
  3. At this page, the user enters their credentials and if they are correct, the IdP redirects the user to the callback URL on the API callback (/api/callback).
  4. The request received on the callback has the state parameter (which we should verify with the one we set on the session previously) and a code parameter. We exchange the code for the identity token with the authorization server/IdP (we also receive access/refresh tokens from the auth server, which we discard for now because we do not want to access any APIs on the behalf of the user).
  5. The identity token is parsed to verify user identity against our database (maybe an email). Assume that the identity is verified.

-- The next part is what's giving me trouble --

  1. The documentation that I have read advises that from here we redirect the user to a URL (e.g. the profile page)and start a login session between the user agent and the API. This is fine for this specific architecture (with both the SPA/static-site being hosted on the same domain).

But how does it scale?

  1. Say I want to move from a session based flow to a JWT based flow (for authenticating to my API).
  2. What if a mobile application comes into the picture? How can it leverage a similar SSO functionality from my API?

NOTE: I have read a little on the PKCE mechanism for SPAs (I assume it works for JAMStack as well) and native mobile apps, but from what I gather, it is an authorization mechanism that assumes that there is no back-end in place. I can not reconcile PKCE in an authentication context when an API is involved.


Solution

  • Usually this is done via the following components. By separating these concerns you can ensure that flows work well for all of your apps and APIs.

    BACKEND FOR FRONTEND

    This is a utility API to keep tokens for the SPA out of the browser and to supply the client secret to the token service.

    WEB HOST

    This serves unsecured static content for the SPA. It is possible to use the BFF to do this, though a separated component allows you to serve content via a content delivery network, which some companies prefer.

    TOKEN SERVICE

    This does the issuing of tokens for your apps and APIs. You could use Google initially, though a more complete solution is to use your own Authorization Server (AS). This is because you will not be able to control the contents of Google access tokens when authorizating in your own APIs.

    SPA CLIENT

    This interacts with the Backend for Frontend during OAuth and API calls. Cookies are sent from the browser and the backend forwards tokens to APIs.

    MOBILE CLIENT

    This interacts with the token service and uses tokens to call APIs directly, without using a Backend for Frontend.

    BUSINESS APIs

    These only ever receive JWT access tokens and do not deal with any cookie concerns. APIs can be hosted in any domain.

    SCALING

    In order for cookies to work properly, a separate instance of the Backend for Frontend must be deployed for each SPA, where each instance runs on the same parent domain as the SPA's web origin.

    UPDATE - AS REQUESTED

    The backend for frontend can be either a traditional web backend or an API. In the latter case CORS is used.

    See this code example for an API driven approach. Any Authorization Server can be used as the token service. Following the tutorial may help you to see how the components fit together. SPA security is a difficult topic though.