Skip to content

Development authentication and authorization

Tiffany edited this page Sep 24, 2020 · 25 revisions

Authentication

The application uses Okta to provide authentication. Users must register for an Okta account and be added to the application user group. Users must also sign up for MFA with Okta.

Authentication is a multi-step process. The front-end uses the @okta/okta-auth-js node module to create an oktaClient that is used to interact with Okta. First, oktaClient.signIn sends the user's username and password to Okta. Okta returns a transaction with the status 'MFA_REQUIRED'. This transaction also includes a array of factors that will contain a factor with a provider 'OKTA'. That factor has a function named verify that will send the one-time password to the user when it is called. The user is then redirected to the page where they will enter the one-time password that they received from Okta.

Once the user enters the one-time password, oktaClient.tx.resume is used to continue the previous transaction, which has a verify function that takes the one-time password. If this one-time password is valid, Okta returns a transaction with the status 'SUCCESS' and a session token. The front-end then calls oktaClient.token.getWithoutPrompt using the session token and a state token generated by the front-end. Okta returns the same state token and the access tokens. If the state token matches with the one that was sent, the tokens are stored in local storage using oktaClient.tokenManager.add('accessToken', accessToken).

!

(see the API configuration documentation for more info on environment variables).

On subsequent calls to the API, the JWT is presented via the 'Authorization' header of each request. The token is verified using the @okta/jwt-verifier node module. The JWT is sent to Okta and if valid, then the user's claims are returned. Then the user is retrieved from Okta using the @okta/okta-sdk-nodejs node module. Permissions are then pulled from the database to create the full user object.

!

Authorization

We're using a combination of role-based and activity-based authorization: the actual logic of authorizing requests is purely activity-based, but to simplify administration, we're using roles to group activities. A user has a role, and that role identifies what activities they have permission to perform.

Our list of roles and activities is available and updated as new ones are introduced.

How it works technically

Data model

There are three tables to support this authorization model. auth_activites is the full list of activities known to the system. auth_roles is, likewise, the full list of rules. The roles map directly to groups in Okta. Finally, there is auth_role_activity_mapping that maps a role to a set of activities.

The server

A user is assigned a role. When a request arrives from the user, the JWT is decrypted and a user object is created, as described in the authentication documentation. During that user creation step, the user's role is fetched from the database, and that role is then mapped to a list of activities. Finally, the list of activities is attached directly to the user object.

From there, we have an authorization middleware called can. Each endpoint can register that it needs authorization by adding a call to the can endpoint during its setup, along with the activity the user needs in order to be authorized:

express.get('/my/path', can('dance-like-nobody-is-watching'), function(req, res, next) { ...

The can middleware will first use the loggedIn middleware to make sure the user is logged in, and then it will verify that the user object has the requested activity.

case result
not logged in an HTTP 403 status is sent, and the endpoint handler is never called
logged in, does not have permission an HTTP 401 status is sent, and the endpoint handler is never called
logged in, has permission the endpoint handler is called
Clone this wiki locally