Skip to content

Authentication Flow

JafarMaash edited this page Mar 20, 2022 · 12 revisions

Abstract Process

We are using Google OAuth to easily authenticate users into our application. Listed below are the steps of the authenticating process.

  • When the user clicks on the 'Sign in with Google' button on the main screen of our React application (/login), they are redirected to a page where they can enter their Google credentials.
  • If the credentials are successful, Google provides the application with an id_token which is a JWT token that identifies our user.
  • Thereafter, this token is sent to the backend server, where we verify the authenticity of the JWT using the Google library.
    • If the token is invalid, it is discarded and an HTTP status code of 401 is returned.
    • If the token is valid, further checks are done using the user's email address to check whether the user has logged in previously and is registered in the database.
      • If the user has not been registered before in our database, then new resources are created for the user such as a new User entity instance.
      • Thereafter, a custom JWT is created by our system and returned back to the client, so it can be attached with the subsequent requests. An HTTP status code of 200 is returned.

A subset of this process is illustrated using the diagram below.

Implementation Details

Google Cloud Platform

To validate Google ID tokens you will need to use their API. To set up an account for this, see here

AuthContextProvider

The responsibility of AuthContextProvider is to ensure that only the authenticated users can get access to the restricted parts of the website.

When a user logs into the application from the Login page component, we invoke the setTokenId method in the onSuccess callback which takes the JWT given to us by Google. When the token ID is updated in AuthContextProvider using setTokenId, there will be a backend call to /api/users/login. In this, the backend would generate an internal custom JWT and return it to the client application. This JWT is then persisted from AuthContextProvider using the local storage of the browser, so it can be transferred in the subsequent requests.

When the backend validates a JWT, the frontend application changes the global axios object so that all outgoing requests are attached with an authorization header.

AuthRoute

We are using the AuthRoute component (along with <Outlet/>) to display routes that require user authentication. This component uses the context mentioned above to check whether the user has been authenticated.

HandlerInterceptor

All requests made to the API are pre-handled in order to validate the JWT used to authenticate the request (except for POST: api/users/login, since users do not have a JWT when hitting that endpoint; it is the one that generates and provides the JWTs). This is done in the interceptor.UserInterceptor class, which extends Spring's HandlerInterceptor. The token is taken from the request and validated in the JwtTokenUtil class, which parses the claims using a secret. If the token is rendered valid, then the client User ID is stored in the HTTP session and the request is passed on to the appropriate controller.

Endpoints

  • GET:/api/validate

    • Authorization Header: token
    • Response
      • 201 No Content: when the token is valid
      • 401 Unauthorized: when the token is invalid
  • POST:/api/users/login

    • Authorization header: Google ID token
    • Response:
      • 200 OK: ID token validated, login success
      • 401 Unauthorized: invalid ID token
    • Response payload:
      • custom JWT if success, to be used for future requests.