Authentication and Authorization
Authentication is the act of validating that users are whom they claim to be.
Authorization is the process of giving the user permission to access a specific resource or function.
The template demo app is set up with OAuth2 authentication and no authorization (Any authenticated user can do anything).
There are 3 main parts that are important to understand in this authentication setup. The OAuth2 authentication server, the SPA web app, and the REST-API.
The Authentication Server
There are many different authentication providers (Keycloak, Azure, Google, Facebook, etc.), running their own implementation of the OAuth2 protocol. It is these servers' responsibility to have the user login and authenticate themselves. The authentication servers authenticate the users against their connected user database (e.g. Azure Active Directory).
If the user authenticate successfully, the auth server returns a signed JSON Web Token (JWT).
In Equinor, Azure AD is the go-to auth provider. This guide helps you register an "App" on Azure and configure it for use with a Web App.
Single Page Web Application
Clients that has a goal of obtaining an access token (JWT), has many different flows/methods to chose from. For Single Page Web Application the recommended flow is the Authorization Code flow with PKCE.
There are no resources to protect in the web app, therefore the job for the SPA in regards to authentication is simple;
- Is the user logged in?
a) No - Redirect user to authentication server for authentication
b) Yes - Add the JWT to every request to the API
Details needed to implement this can be found here; https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
We use the react-oauth2-pkce library to handle this for us.
REST API
The REST API (e.g python FastAPI server) has access to, and is responsible for serving, data that could be private. Therefore we need to validate that the request is coming from an authenticated client.
We do that in these steps;
- Require a JWT on each request
- Fetch the RSA public keys from the authentication server.
- Validate the JWT signature with the auth server's public key
FastAPI has system called dependencies that is well suited for running a specific function before every request.
Here is an example;
app = FastAPI(title="Awesome Boilerplate") # Create the FastAPI app
routes.include_router(report_router) # Add a route/controller to the app
# Add the 'auth_with_jwt()' function as a dependency
# This function will do the actual JWT validation with step 2 and 3
app.include_router(routes, dependencies=[Security(auth_with_jwt)])
That's it! Now every route added like this will require a successfull JWT validation before the request will be processed.
Dependencies can also return values, useful if you need to do some kind of authorization.
Here is one example;
@router.delete("/{id}", operation_id="delete-report")
def delete_report(id: str, user: User = Depends(auth_with_jwt)):
if has_permission(user):
delete_report(id)
return "OK"
else:
return PlainTextResponse("Permission denied", status_code=402)