identity-auth-server-sdk v2.0.0
Identity Auth Server SDK
Table of Contents
- SDK overview
- What are the session cookies?
- User Migration
- Configuring Identity Auth Server
- AWS Credentials
- Instantiating an SDK instance
- Options
dynamoDb
[object]cognito
[object]cognito.adminUserClient
[object]cognito.adminUserClient.authorizationCallbackRedirectUrl
[string]cognito.adminUserClient.clientId
[string]cognito.adminUserClient.clientSecret
[string]cognito.adminUserClient.domain
[string]cognito.adminUserClient.finalRedirectUrl
[string]cognito.adminUserClient.region
[string]cognito.adminUserClient.userPoolId
[string]
cognito.activationFailRedirectUrl
[string]cognito.activationSuccessRedirectUrl
[string]cognito.authorizationCallbackRedirectUrl
[string]cognito.clientId
[string]cognito.clientSecret
[string]cognito.domain
[string]cognito.finalRedirectUrl
[string]cognito.logoutRedirectUrl
[string]cognito.machineUserClient
[object]cognito.pinpointId
[string]cognito.region
[string]cognito.userPoolId
[string]
cookie
[object]customErrorFormatter
[function]logger
[object ]metabase
[object]securityOptions
[object]
- DynamoDB (enhanced security mode only)
- API endpoints
- Auth code exchange for Cognito tokens
- High-risk refresh token flow
- Low-risk refresh token flow
- User registration hook
- Activation Endpoint
- Middleware
- Authorizer middleware
- CSRF token authorizer middleware
- Decrypting Things
- JWT Verifier
- Direct calls to Cognito
- Metabase
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. ieAuthorization: Bearer <jwt access token>
- ID token can be provided in a
X-VENTURES-ID-TOKEN
header. ieX-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.