2.0.0 • Published 5 years ago

identity-auth-server-sdk v2.0.0

Weekly downloads
-
License
ISC
Repository
-
Last release
5 years ago

Identity Auth Server SDK

Table of Contents

SDK overview

The backend SDK is a library compatible with Node.js + Express, and provides route handlers and middleware to abstract the following authentication-related functionality

  • auto-wires endpoints for handling all supported authentication methods (SRP, OAuth, plain password auth)
  • on authentication:
    • retrieves access, id and refresh tokens from Cognito and sets tokens as httpOnly cookies in the browser client
    • generates CSRF token and sets token as a cookie in the browser client
  • handles user session initiation
  • handles session invalidation via auto-wired logout endpoint
  • auto-wires endpoints for refreshing access and id tokens
  • provides middleware to validate the access and CSRF tokens

NOTE: This SDK is tightly coupled to the identity-auth-web-sdk. Many of the endpoints here are designed to work with the web SDK. All you need to do is config and implement the server SDK, and point the web SDK to the appropriate url prefix. This simplifies a lot of the work for authenticating. See the web sdk for more information.

What are the session cookies?

Session cookies consist of:

  • httpOnly access token
  • id token
  • httpOnly refresh token (for low-risk application only) cookie
    • The refresh token is only sent to the refresh endpoint configured on the backend
  • readable CSRF token to be replayed back in headers on each request

Separately, via some other mechanism, a CSRF token will be shared on successful authentication.

Example of httpOnly access token signature - Note that only the server can see the content of this cookie.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRoX3RpbWUiOjE1NzM3MzUzMTQsImV4cCI6MTU3MzczODkxNCwic3ViIjoiNDY5OGI3ZjItMWMyYi00NzM5LWIzNTgtZGQ4ZjI4NGZiMGNjIiwidXNlcm5hbWUiOiI0Njk4YjdmMi0xYzJiLTQ3MzktYjM1OC1kZDhmMjg0ZmIwY2MifQ.Sr2zPsHiOnbSTAjh6jSmu1HgjIrKcqbxk6caqA54Vio

The session cookies are required when hitting a secure endpoint or refreshing a user's session.

To ensure that sensitive cookies are not accessible by javascript code on the client-side, they must be transmitted via httpOnly and only through HTTPS. The cookies need to be signed using a secret and have to be bounded by the server domain and possibly a path. The @rbcv/identity-auth-server-sdk will handle all the of these.

User Migration

User migration is handled by lambdas attached directly to your Cognito instance. See this repository for details.

IMPORTANT You must use OAuth or plain text password auth when migrating users. Migration won't work with SRP since Cognito will never receive the user's password. All of the routes for these authentication methods are wired up automatically by this SDK. You can choose which one to use through your front end application with the web sdk.

Configuring Identity Auth Server

AWS Credentials

Under the hood, our Identity Auth Server SDK uses the AWS SDK for some operations to communicate with the Cognito user pool. This AWS SDK requires that providing AWS credentials through one of the following means.

This SDK requires that you provide the credentials through one of the following means.

  • Loaded from AWS Identity and Access Management (IAM) roles for Amazon EC2
  • Loaded from the shared credentials file (~/.aws/credentials)
  • Loaded from environment variables

See the official documentation for details.

Instantiating an SDK instance

The recommended approach is to call the exported setConfig function early on in your application lifecycle. Then the module can be imported anywhere you need it, and module caching will ensure you get the same configured instance each time.

Example

Directory structure

- helpers/
--- cognito.js
- routes/
--- handleAuthRegister.js
- server.js
- initializeIdentityAuth.js
- config.js

./config.js

{
  cognito: {
      userPoolId: 'ca-central-1_987654321',
      clientId: 'SIIe4355E5sZiYVm',
      authorizationCallbackRedirectUrl: 'rIA4nZB06s9x5wrCGKOU1RTS',
      finalRedirectUrl: 'localhost:3000',
      activationSuccessRedirectUrl: 'localhost:3000/activationSuccess',
      activationFailRedirectUrl: 'localhost:3000/activationFailed',
      logoutRedirectUrl: 'localhost:3000',
      domain: 'localhost',
      region: 'ca-central-1',
  },
  cookie: {
      domain: 'localhost',
      path: '/refresh'
  },
  securityOptions: {
      aesKey: 'ABCDEFFFABCDEFFFABCDEFFFABCDEFFFABCDEFFFABCDEFFFABCDEFFFABCDEFFF,
      hashSalt: 'salty',
  },
  customErrorFormatter: (code, error) => ({
        statusCode: code,
        message: error.message,
        metadata: error.meta
    })
}

./server.js

const identityAuthSDK = require('@rbcv/identity-auth-server-sdk');
const express = require('express');
const handleAuthRegister = require('./routes/handleAuthRegister');

// Initialize SDK config early
require('./init-identity-auth');

const app = express();

/**
 * Load access token and CSRF token validation middleware
 * Note: the example shows the middleware being applied 
 * only to routes prefixed by "/protected"
 */
app.use(
    '/protected',
    [
        identityAuthSDK.middleware.appUserAuthMiddleware,
        identityAuthSDK.middleware.csrfMiddleware
    ]
);

/**
 * Automatically wire route handlers for
 * the following routes each prefixed by "/identity-auth"):
 *
 * - /activate
 * - /logout
 * - /metabase/callback
 * - /metabase/init
 * - /oauth/callback
 * - /oauth/init
 * - /oauth/admin/callback
 * - /srp/auth
 * - /srp/init
 * - /user-password-auth
 *
 * Note: the routes prefix can be set to any value that ensures no collision
 * with other routes defined in your app.
 */
app.use('/identity-auth', identityAuthSDK.routes);

// Other authentication route handlers
app.post('/register', handleAuthRegister);
app.post('/other-auth-endpoint', handleOtherAuthEndpoint);

app.listen(3000);

./routes/handleAuthRegister.js

const express = require('express');
const cognitoHelper = require('./helpers/cognito');

const router = express.Router();

router.post('/', async (req, res) => {
   try {
       const { email, password } = req.body;
       const { appClientId, cognitoClient, getSecretHash } = cognitoHelper;
       const secretHash = getSecretHash(email);
       
       const signUpOptions = {
           ClientId: appClientId,
           SecretHash: secretHash,
           Password: password
       };

       await cognitoClient.signUp(signUpOptions).promise();
       
       res.status(201).end();
   } catch (error) {
       // handle error
   }
});

./initializeIdentityAuth.js

const identityAuthSDK = require('@rbcv/identity-auth-server-sdk');
const config = require('./config.js');
identityAuthSDK.setConfig(config);

./helpers/cognito.js

const identityAuthSDK = require('@rbcv/identity-auth-server-sdk');

const cognitoService = new identityAuthSDK.services.Cognito();
const cognitoClient = cognitoService.getCognitoClient();

/**
 * Retrieve your Cognito client config from your app application config
 * or from environment variables
 */
const appClientId = process.env.COGNITO_CLIENT_ID;
const appClientSecret = process.env.COGNITO_CLIENT_SECRET;

/**
 * All operations on the user require a secret hash to be generated
 * and passed to the Cognito client. The secret hash is generated from the user's
 * username (or email address) and your Cognito app clientId and clientSecret
 */
const getSecretHash = cognitoService.getSecretHash(username, appClientId, appClientSecret);

module.exports = {
    appClientId,
    cognitoClient,
    getSecretHash
};

Options

dynamoDb object

Configuration for DynamoDb. Only required when securityOptions.mode is set to enhanced.

Required: false

This is used to store refresh tokens rather than setting them as cookie. Only applies for enhanced security mode.

dynamoDb.endpoint string

Required: true

DynamoDB endpoint.

dynamoDb.region string

Required: true

AWS region of your DynamoDB instance.

dynamoDb.table string

Required: true

Name of your table for storing refresh tokens in DynamoDB.

cognito object

Details that are required ot authenticate to Cognito, verify cognito tokens, gather metrics, etc

Required: true

Details that are required ot authenticate to Cognito, verify cognito tokens, gather metrics, etc

cognito.adminUserClient object

Required: false

A Cognito client that can be configured separately from the main user pool.

This user pool is used for administrators of your application. You can configure this so that you can validate tokens generated by the admin user pool. Because this user pool is separate from the 'app user' user pool, admins will require different credentials to sign in.

cognito.adminUserClient.authorizationCallbackRedirectUrl string

Required: true

The url to redirect back to your application to complete the OAuth flow. If you're using the SDK to complete the auth flow this, the value should be {your_app_base_url}/{sdk_prefix}/oauth/callback

Note: since the auth flow is completed on your backend so that cookies can be set, this parameter is only used to make Cognito happy. Your true redirect URL is finalRedirectUrl

cognito.adminUserClient.clientId string

Required: true

Client ID of the admin user client: AWS Console > Cognito > YOUR_USER_POOL > App clients > App client id

Like cognito.clientId but this is for a client that sits within your admin user pool.

cognito.adminUserClient.clientSecret string

Required true

Client secret of the admin user client: AWS Console > Cognito > YOUR_USER_POOL > App clients > App client secret

Like cognito.clientSecret but this is for a client that sits within your admin user pool.

cognito.adminUserClient.domain string

Required: true

Domain name configured for the Admin user pool. Can be a custom domain or a something following the format of https://{custom_prefix}.auth.{region}.amazoncognito.com.

You can find this under AWS > Cognito > YOUR_USER_POOL > App Integration > Domain Name

It tells the SDK where to find the hosted UI and endpoints for OAuth.

cognito.adminUserClient.finalRedirectUrl string

Required: true

This is the url to which the user is redirected after the authentication flow has completed. Usually the main page of your application.

cognito.adminUserClient.region string

Required: true

AWS region for admin user Cognito user pool.

cognito.adminUserClient.userPoolId string

Required true

Cognito user pool id for the admin users: AWS Console > Cognito > YOUR_USER_POOL > General settings > Pool Id

This user pool is completely separate from your cognito.userPoolId. It will have it's own JWT signing key, and tokens can't be used between user pools. Admins can authenticate to this pool, and use the tokens generated for elevated/different access compared to the user pool.

cognito.activationFailRedirectUrl string

Required: false

On failed activation using the auto wired SDK routes, the server will return a 302 redirect to this URL.

If activation fails, the user will not be able to log in with Cognito. If you need custom logic around this event, look into implementing custom logic with the Cognito client under direct calls to Cognito

cognito.activationSuccessRedirectUrl string

Required: true

On successful activation using the auto-wired SDK routes, the server will return a 302 redirect to this URL

This activates the user in Cognito. The user should now be able to authenticate successfully to Cognito. If you need custom logic around this event, look into implementing custom logic with the Cognito client under direct calls to Cognito

cognito.authorizationCallbackRedirectUrl string

Required: true

The redirect url for Cognito to send the auth code in the OAuth Flow. This should match what you have configured in Cognito: AWS Console > Cognito > YOUR_USER_POOL > App client settings > Callback URL(s).

Note: since the auth flow is completed on your backend so that cookies can be set, this parameter is only used to make Cognito happy. You true redirect URL is finalRedirectUrl

cognito.clientId string

Required: true

The id of a client configured inside your user pool: AWS Console > Cognito > YOUR_USER_POOL > App clients > App client id

An app is an entity within a user pool that has permission to call unauthenticated APIs (APIs that do not have an authenticated user), such as APIs to register, sign in, and handle forgotten passwords. To call these APIs, you need an app client ID and an optional client secret.

Official documentation here

cognito.clientSecret string

Required: true

The client secret for your Cognito client: AWS > Cognito > YOUR_USER_POOL > App clients > App client secret

cognito.domain string

Required: true

The URL of your Cognito instance. ie https://<custom_name>.auth.<region>.amazoncognito.com. It can also be completely custom.

You can find this under AWS > Cognito > YOUR_USER_POOL > App Integration > Domain Name

This tells the SDK where to find the hosted UI and the endpoints used for OAuth.

cognito.finalRedirectUrl string

Required: true

The redirect url after Cognito tokens are retrieved for OAuth flow. Usually points to your frontend. Cookies are now set at this point.

cognito.logoutRedirectUrl string

After calling the logout endpoint, new empty cookies are set and a 302 redirect is returned to this URL.

cognito.machineUserClient object

Required: false

A Cognito client that can be configured separately from the main user pool.

You can have any number of user pools and clients within those user pools. If you need to authenticate system to system calls, and you use a separate user pool, you can configure that here.

cognito.machineUserClient.clientId string

Required: true

Client ID of the machine user client: AWS Console > Cognito > YOUR_USER_POOL > App clients > App client id

Like cognito.clientId but this is for a client that sits within your machine user pool.

cognito.machineUserClient.clientSecret string

Required true

Client secret of the machine user client: AWS Console > Cognito > YOUR_USER_POOL > App clients > App client secret

Like cognito.clientSecret but this is for a client that sits within your machine user pool.

cognito.machineUserClient.region string

Required: true

AWS region for machine user Cognito user pool.

cognito.machineUserClient.userPoolId string

Required true

Cognito user pool id for the machine users: AWS Console > Cognito > YOUR_USER_POOL > General settings > Pool Id

This user pool is completely separate from your cognito.userPoolId. It will have it's own JWT signing key, and tokens can't be used between user pools. Machines can authenticate to this pool, and use the tokens generated for elevated/different access compared to the user pool.

cognito.pinpointId string

Required: false

Amazon Pinpoint ID used internally by the SDK to track metrics related to user actions to Cognito. If not provided, no metrics are gathered.

Amazon Pinpoint is a service that lets you gather metrics about your user's activity. More information can be found here.

cognito.region string

Required: true

The AWS region of your Cognito instance.

cognito.userPoolId string

Required: true

The Cognito User Pool ID. Required to communicate to Cognito: AWS > Cognito > YOUR_USER_POOL > General settings > Pool Id. This is used to preconfigure a Cognito client that is used internally by the SDK, and be accessed through the Cognito service exported by the SDK.

The Cognito user pool is the directory that stores all your users. It provides identity services and facilitates logins through social identity providers. Official documentation here

cookie object

Configuration for how cookies are set when using pre configured Express routes

Required: false

Properties that control how cookies are set by the SDK

All of these properties are standard properties on cookies. Documentation about them can be found here

cookie.maxAgeSeconds number

Default: 86400

Required: false

Cookie expiration for all cookies. This should align with expiration of your refresh token in Cognito.

cookie.domain string

Default: localhost

Required: true

Default: The base domain for the cookies. ie example.com

cookie.isSecure string

Default: true

Required: false

Determines if the secure flag will be set for all cookies. You might want to disable this for local development.

cookie.sameSite string

Default: Lax

Required: false

SameSite setting on the cookies. The recommended setting is Strict but not necessary if you are using CSRF tokens.

cookie.path string

Required: false

The path for the refresh endpoint. This will set the path on the refresh token cookie so that it only works on one endpoint. Not required if the app's securityOptions.mode is set to enhanced.

This property only applies to the refresh token. For all other cookies the path property is not set.

securityOptions object

Security related options.

Require: true

Options for how encryption and refresh tokens are handled.

securityOptions.aesKey string

Required: true

An encryption key used for encrypting refresh tokens and usernames in transactional emails. The format is a 64 character HEX string.

You can generate your own AES Key and store it securely (ie in AWS Secrets Manager).

Example to generate a 64 character hex key in Javascript

// 32 bytes will result in a 64 character string because 1 hex character is 4 bits
const aesKey = crypto.randomBytes(32).toString('hex');

When using the auto-wired SDK endpoints, refresh tokens are encrypted when you login. The refresh endpoint is also expecting an encrypted refresh token. The activation endpoint also expects an encrypted username, and password reset emails contain encrypted usernames that need to be decrypted server side.

securityOptions.discardRefreshToken boolean

Default: false

Required: false

If true, the SDK will not return a refresh token on login. It will also not attempt to store the refresh token in DynamoDB. Essentially it disables access token refresh.

securityOptions.hashSalt string

Required: true

A salt used for generating a csrf token. Can be any string. This token is not a secret, and does not require special handling.

The salt is generated using a piece of the access token, plus this salt. When you get a new access token, you'll also receive a new CSRF token.

securityOptions.mode string

Default: standard

Allowed values: standard or enhanced

Required: false

If set to standard, refresh tokens are set as cookies. If enhanced, the refresh tokens are stored in DynamoDB.

The bank recommends you choose enhanced if your application moves money. The downside to this is that once a session has gone stale for 1 hour, the refresh token will be lost and your user will need to sign in again. There is also a separate configuration like this in the web sdk that affects how your frontend will work. You may also choose to disable refresh altogether with securityOptions.discardRefreshToken.

logger object

Log configuration for logs produced by the sdk

Required: false

Configure the verbosity of the logger.

logger.level string

Default: warn

Required: false

Configure the log level for the SDK

The SDK uses RBC Ventures common logging library internally for logs.

metabase object

Metabase authentication configuration ( REQUIRES ADMIN POOL CONFIGURED )

Required: false

This will configure endpoints in the SDK that will allow you to use JWT base authentication with Metabase.

It is like the regular OAuth flow provided by this SDK, with the exception that you'll get a JWT that is signed by Metabase, and you'll be redirected to Metabase with your JWT in the query parameters at the end.

metabase.secret string

Required: true

Metabase JWT signing key for JWT based authentication

The SDK will sign a JWT with Metabase's signing key after you successfully authenticate to Cognito. Metabase will verify its authenticity and grant access tokens.

metabase.url string

Required: true

Metabase base URL, ie https://metabase.ventureDomain.com

After successful authentication to Cognito, you'll be redirected to your Metabase instance defined by this URL. You only need to provide the base URL. The SDK redirect you to the correct Metabase endpoint with the JWT in the query parameters.

customErrorFormatter function

Custom error formatter. A function that takes an http status code, and error object.

Required: false

Example:

const config = {
    customErrorFormatter: (code, error) => ({
        statusCode: code,
        message: error.message,
        metadata: error.meta
    }),
    //... rest of your config here
}

If you have error handling middleware in your application, you can configure how errors are thrown by the SDK. The above example would translate the error object into an object that your error handling middleware might expect.

If you don't provide any custom error handler, the SDK will call express' next function with

  {
       statusCode: 400,
       message: errorMessage,
       meta: {},
  }

DynamoDB (enhanced security mode only)

For high risk applications, the refresh token should not be stored on the browser or mobile storage. Instead, the refresh token will be stored in DynamoDB.

The Platform team will coordinate with RBC DevOps to deploy DynamoDB into the Venture's VPC in a private subnet through a cloud formation script.

API endpoints

Note: the web sdk will handle calling these authentication endpoints.

Learn more about the authentication flow.

Auth Init

The oauth/init endpoint is called to generate the PKCE code challenge, code verifier and redirect to https://AUTH_DOMAIN/oauth2/authorize. This is the starting point for the authorization code flow.

Sample request

GET https://<venture-backend-domain>/auth/oauth/init

Sample response

HTTP/1.1 302 found
Content-Type: application/json
Location: https://AUTH_DOMAIN/oauth2/authorize
Set-Cookie: pkce_verifier="..."; Expires; Domain; Secure; HttpOnly; SameSite=Lax

Auth code exchange for Cognito tokens

Cognito calls the oauth/callback endpoint to send the authorization code. The authorization code and the PKCE code verifier are used to complete the authentication process.

Request Parameters

Query Parameters

code

    The authorization code

code_verifier

    The PKCE code verifier

Sample request

GET https://<venture-backend-domain>/auth/oauth/callback?code=AUTHORIZATION_CODE&code_verifier=PKCE_CODE_VERIFIER

Sample response from Cognito

The request returned by cognito is translated to store the tokens in cookies.

HTTP/1.1 200 OK
Content-Type: application/json

{
    "access_token":"eyJz9sdfsdfsdfsd",
    "refresh_token":"dn43ud8uj32nk2je",
    "id_token":"dmcxd329ujdmkemkd349r",
    "token_type":"Bearer",
    "expires_in":3600
}

Sample response back to the browser

HTTP/1.1 302 Found
Location: ${finalRedirectUrl}
Content-Type: application/json
Set-Cookie: access_token=<...>; Expires; Domain; Secure; HttpOnly
Set-Cookie: refresh_token=<...>; Expires; Domain; Secure; HttpOnly; Path
Set-Cookie: id_token=<...>; Expires; Domain; Secure; HttpOnly
Set-Cookie: csrf_token=<...>; Expires; Domain; Secure;

This endpoint redirects to the venture's frontend as configured by finalRedirectUrl

*For low risk application (standard security mode)

The library will package the refresh token into an httpOnly that gets sent back to the browser.

*For high-risk application (enhanced security mode)

The library will save the refresh token on the backend instead of sending it back to the browser.

High-risk refresh token flow

The oauth/refresh endpoint allows high-risk applications to be able to refresh their user's session while a valid access token is still active. It uses the access token to retrieve the session's refresh token from storage.

Note: To prevent CSRF, a request to any authenticated API will require an X-VENTURES-CSRF-TOKEN that is derived from the access token. The backend app will use the CSRF token authorizer middleware to validate that it belongs to the original request.

Request sample

POST https://<venture-backend-domain>/auth/oauth/refresh
Cookie:
    name='access_token'
    name='X-VENTURES-CSRF-TOKEN'

Response sample

HTTP/1.1 200 ok
Content-Type: application/json
Set-Cookie: access_token=<new value>; Expires; Domain; Secure; HttpOnly
Set-Cookie: id_token=<new value>; Expires; Secure;
Set-Cookie: X-VENTURES-CSRF-TOKEN=<new value>; Expires; Domain; Secure; HttpOnly

Low-risk refresh token flow

The oauth/refresh endpoint allows low-risk applications to be able to refresh their user's session while a valid refresh token cookie is still active.

Note: To prevent CSRF, a request to any authenticated API will require an X-VENTURES-CSRF-TOKEN that is derived from a hash of the access token to be set in the headers. The backend app will use the CSRF token authorizer to validate that it belongs to the original request.

Request sample

POST https://<venture-backend-domain>/auth/oauth/refresh
Cookie:
    name='access_token'
    name='refresh_token'
    name='X-VENTURES-CSRF-TOKEN'"

Response sample

HTTP/1.1 200 ok
Content-Type: application/json
Set-Cookie: access_token=<new value>; Expires; Domain; Secure; HttpOnly
Set-Cookie: id_token=<new value>; Expires; Secure;
Set-Cookie: X-VENTURES-CSRF-TOKEN=<new value>; Expires; Domain; Secure; HttpOnly

User registration hook

An endpoint is called by Cognito's lambda function after a new user account has been confirmed (user has registered and activated their account). Cognito's lifecycle event post-confirmation lambda will make a call to this endpoint so that the Venture's backend app can perform housekeeping operations.

You can configure an endpoint in your express app as you normally would, but make sure that endpoint uses the machine user authorizer. See authorizer middleware for details on how to implement the machine user authorizer.

Activation Endpoint

Users are sent emails with links to the application to confirm their email address. The route is automatically wired up at <SDK base path>/activate.

On success the endpoint will return a redirect to the URL specified in activationSuccessRedirectUrl in the cognito config. On failure the endpoint will return a redirect to the URL specified in activationFailRedirectUrl in the cognito config.

Example callback endpoint

router.get('/user-registration-callback', [authorizerMiddleware], (req, res) => {
    // do something here.
})

Sample request from post-confirmation lambda

POST https://<venture-backend-domain>/user-registration
Authorization=Bearer <access_token>
Content-Type='application/json'

Body:
    userProfile: {
        username: ...,
        email: ...,
        ...
    }

See the full list of standard profile attributes here.


Middleware

Authorizer middleware

This middleware is responsible for protecting secure API endpoints by validating that the access token is valid by checking that the claims belong to the correct audience and that the access token has not expired.

How does it verify the access token?

  • Fetches the appropriate JWK from memory to verify the access token's signature
  • Verifies that the JWT authorization bearer token contains the right claims: audience, issuer, userPoolId, and is not expired
  • Extracts the member id and adds it to the X-VENTURES-PRINCIPAL header if the ID token has a member id

If you're not using cookies (native applications), you can provide the tokens in headers.

  • Access token can be provided in the Authorization header. ie Authorization: Bearer <jwt access token>
  • ID token can be provided in a X-VENTURES-ID-TOKEN header. ie X-VENTURES-ID-TOKEN: <jwt id token>

By default, the library will only check that the access token belongs to the right audience, issuer, userPoolId and that it has not expired. If you require an additional check such as checking to see if the user exists on your database, you can provide your own middleware in the router.

Example usage

import identityAuthSDK from '@rbcv/identity-auth-server-sdk';

router.post('/user', [identityAuthSDK.middleware.appUserAuthMiddleware, yourCustomMiddleware], (req, res) => {
    // perform operations here
});

middleware also provides 2 other authorizers in addition to the app user authorizer. The machine user authorizer (middleware.machineUserAuthMiddleware), and the admin user authorizer (middleware.adminUserAuthMiddleware). These can all be configured to use different user pools or app clients. See the cognito section of the configuration sample for more details.

CSRF token authorizer middleware

Since we're using cookies to store the access and id tokens, we'll be implementing a CSRF mitigation middleware to make sure that the request is coming from the original user.

The CSRF token is derived from SHA256(hashSalt + access_token). hashSalt is provided in securityOptions config)

When the client makes a request to one of the secure API endpoints, this middleware will compute the CSRF token and compare to the CSRF token provided in the X-VENTURES-CSRF-TOKEN header (not to be confused with the X-VENTURES-CSRF-TOKEN cookie)

Example usage

import identityAuthSDK from '@rbcv/identity-auth-server-sdk'
const { appUserAuthMiddleware, csrfMiddleware } = identityAuthSDK.middleware;

router.post('/user', [appUserAuthMiddleware, csrfMiddleware], (req, res) => {
    // perform operations here
});

Decrypting Things

Some things, like PII are encrypted, ie in activation emails the username is encrypted. The sdk provides a utility to decrypt them.

Example to decrypt.

import { cryptoUtil } from '@rbcv/identity-auth-server-sdk/lib/util';
const decryptedUsername = cryptoUtil.decrypt(username);

JWT Verifier

If you choose not to use the express middleware for validating the JWT tokens, or are using an incompatible framework, you can import the JWT verification service, and use it directly.

Example:

const sdk = require('@rbcv/identity-auth-server-sdk');

const { verifyIdToken, verifyAccessToken } = sdk.services.TokenVerifier('clientId', 'region', 'userPoolId');

// verifiers return undefined if unable to verify token authenticity
const idTokenClaims = verifyIdToken('some.id.token');
const accessTokenClaims = verifyAccessToken('some.access.token');

Direct calls to Cognito

The sdk provides an AWS.CognitoIdentityServiceProvider client for you. You can use this to talk directly to cognito. This class provides the getSecretHash method which is required if your app clients have a secret configured.

import identityAuthSDK from '@rbcv/identity-auth-server-sdk';

const cognitoService = new identityAuthSDK.services.Cognito();

const client = cognitoService.getCognitoClient();
const SECRET_HASH = cognitoService.getSecretHash('bob', 'clientId', 'clientSecret');

client.doSomeAction({
    SECRET_HASH
})

Registration

await cognitoClient
    .signUp({
        ClientId: clientId,
        Password: password,
        Username: email,
        AnalyticsMetadata: {
            AnalyticsEndpointId,
        },
        SecretHash: cognitoService.getSecretHash(
            email,
            clientId,
            clientSecret
        ),
        UserAttributes: [
        {
            Name: 'given_name',
            Value: firstName
        },
        {
            Name: 'family_name',
            Value: lastName
        },
        {
            Name: 'email',
            Value: email
        },
        {
            Name: 'locale', // required, defaults to `en-CA`, can also be set to `fr-CA`
            Value: 'en-CA'
        },
        {
            Name: 'custom:compliance_id',
            Value: complianceId
        }
        ]
    })
    .promise();

Activation

await cognitoClient
    .confirmSignUp({
        ClientId: clientId,
        ConfirmationCode: token,
        Username: decryptedUsername,
                AnalyticsMetadata: {
            AnalyticsEndpointId,
        },
        SecretHash: cognitoService.getSecretHash(
            decryptedUsername,
            clientId,
            clientSecret
        )
    })
    .promise();

Metabase

How to set up metabase with our sdk

In the sdk config add the following

"adminUserClient": {
    "clientId": "admin-client-id",
    "region": "ca-central-1",
    "userPoolId": "admin-user-pool",
    "domain": "admin-cognito-userpool-domain",
    "authorizationCallbackRedirectUrl": "http://localhost:8080/auth/metabase/callback",
    "clientSecret": "admin-client-secret",
    "finalRedirectUrl": ""
}

"metabase": {
    "secret": "Metabase JWT signing key",
    "url": "Metabase url"
  }

How to configure Metabase to support JWT based authentication

Ref: https://github.com/metabase/metabase/blob/master/docs/enterprise-guide/authenticating-with-jwt.md

In the user attribute configuration, map the following attributes

email attribute -> email
first name attribute -> given_name
last name attribute -> family_name

Metabase authentication

Go to <venture-back-end-domain>/auth/metabase/init on the browser to authenticate against the Cognito admin user pool. Once authenticated, you will be redirected to the the Metabase dashboard.