/civic-login-lambdas

Incorporate Civic Login with AWS Lambda

Primary LanguageJavaScriptMIT LicenseMIT

Civic Login Lambdas

Not using AWS Lambda? You're probably looking for Civic SIP API.

A library to secure AWS Lambda resources using Civic Authentication.

This library allows you to protect your AWS Lambda endpoints with a custom authorizer. The session token is generated by calling the /login function, passing a single-use login token generated by Civic. This login token contains details about the user logging in, that you can then validate, store or send to the front-end

For information on how to integrate your front-end with civic, see https://docs.civic.com and additionally, https://github.com/civicteam/civic-login-redux for a Redux example.

Installation

run npm install @civic/login-lambdas

Example

See /example for an example using the Serverless framework.

Getting Started

Step 1. Generate a session token key pair using:

node node_modules/civic-login-lambdas/scripts/generateSessionTokenKeyPair.js

The session token signer uses an ECDSA signing algorithm using the secp256r1 ECC curve. Note - the private key must be kept secret. If it is compromised, an attacker could generate a token that would be accepted by your back-end services.

Step 2. Add a login handler:

loginHandler.js

const authFactory = require('civic-login-lambdas');

const config = // SEE BELOW;

// add login business logic here - acceptable return values are objects, promises or null.
// UserData contains the details that you request from the user (see /example for a sample loginCallback)
// any return values will be added to the login response to the client
const loginCallback(event, userData) = () => null;

module.exports = authFactory(logger, config.login, loginCallback);

where config is a JS object that looks like:

{
    login: {
      app: {
        appId: The appId you receive when setting up your app on integrate.civic.com,
        appSecret: The app secret from integrate.civic.com*
        pubKey: Your signing public key from integrate.civic.com
        prvKey: Your signing private key from integrate.civic.com*
        encPubKey: Your encryption public key from integrate.civic.com
        encPrvKey: Your encryption private key from integrate.civic.com*
      },
      sessionToken: {
        issuer: your-app,
        audience: your-app-url,
        prvKey: the session token private key*
        pubKey: the session token public key
        resourceArn: [Optional] the ARN pattern of the lambdas that should be authorised using this session token (see below: sessionAuthorizer policy generation)
      }
    }
}

Note - the session token issuer and audience are not used in the token verification. You can set any values you like here. The subject of the token will be the user ID passed from Civic.

  • Warning - the appSecret, prvKey, encPrvKey and sessionToken.prvKey are all sensitive values that could compromise the security of your application if shared. These should not be checked in to source code.

Step 3. Register the login handler lambdas

If using the Serverless Framework:

serverless.yml

functions:
  # The login endpoint to pass the civic login token to.
  # The response contains a session JWT token
  login:
    handler: handler.login
    events:
      - http:
          path: login
          method: POST
  # post the session token here to retrieve a renewed one
  keepAlive:
    handler: handler.keepAlive
    events:
      - http:
          path: session/keepAlive
          method: POST
  # a custom authorizer that validates the JWT token
  sessionAuthorizer:
    handler: handler.sessionAuthorizer

  # your lambda, secured using civic-login
  my-secured-lambda:
    handler: handler.helloworld
    events:
      - http:
          path: hello
          method: GET
          authorizer: sessionAuthorizer

The session token

The session token contains by default a unique user ID generated by Civic. To obtain this user ID in the loginCallback, call:

const loginUtil = require('civic-login-lambdas/src/util');

const userId = loginUtils.getUserIdFromUserData(userData);

You can customise the session token contents by returning an object of type LoginData from the loginCallback e.g.

const LoginData = require('civic-login-lambdas/src/loginData');

function loginCallback(event, userData) {
  // this will be passed back as the response from the login call
  const loginResponse = {
    someKey: 'someValue'
  }

  // this will be added to the session token payload
  const sessionTokenContents = {
      userId: myGeneratedUserID
  }

  return new LoginData(loginResponse, sessionTokenContents);
}

sessionAuthorizer policy generation

The session authorizer validates the session token and generates a policy document that tells API Gateway which lambdas can be accessed by the session token. By default, this is the methodARN on the event, i.e. the ARN of the lambda which is being accessed.

This works fine, except that the policy document is then cached by API Gateway for a particular session token. This means that if you then use the same session token for a second lambda, API Gateway retrieves the cached policy document that allows access only to the first lambda, and denies access.

There are two ways to solve this:

  1. Prevent caching of the policy by setting the TTL to 0 (see here) This has an effect on performance.

  2. Set the resourceARN to cover all lambdas that you wish to authorise in the sessionToken config 'resourceARN' property.

The property should look something like this:

arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function/${self:service}-${opt:stage, self:provider.stage}-*

You can either set this explicitly in the configuration (substituting all the variables above), or add the above line to serverless.yml as an environment variable (use the serverless-cf-vars plugin to substitute the AWS properties), and refer to that environment variable in your code.

Linting

This project is using the airbnb linting rules.

Run the following to lint the project

npm run lint

Testing

This project was tested using chai.js and the expect assertion library

Run the following to test the project

npm test