keycloak-lambda-authorizer
TypeScript icon, indicating that this package has built-in type declarations

1.0.10 • Public • Published
  • CircleCI
  • npm version
  • Coverage Status
  • Maintainability
  • Node.js 12.x, 14.x, 15.x, 17.x CI
  • Bugs
  • Technical Debt
  • Security Rating

Description

Implementation Keycloak adapter for Cloud

Features

Installation

npm install keycloak-lambda-authorizer -S

Examples

How to use

Role Based

import  KeycloakAdapter from 'keycloak-lambda-authorizer';

const keycloakJSON = ...; // read Keycloak.json

const keycloakAdapter = new KeycloakAdapter({
  keycloakJson: keycloakJSON,
});

export async  function authorizer(event, context, callback) {

    const requestContent = await keycloakAdapter.getAPIGateWayAdapter().validate(event, {
     role: 'SOME_ROLE',
    });
}

Client Role Based

import  KeycloakAdapter from 'keycloak-lambda-authorizer';

const keycloakJSON = ...; // read Keycloak.json

const keycloakAdapter = new KeycloakAdapter({
  keycloakJson: keycloakJSON,
});

export async  function authorizer(event, context, callback) {
    const requestContent = await keycloakAdapter.getAPIGateWayAdapter().validate(event, {
     clientRole:{role: 'SOME_ROLE', clientId: 'Client Name'}
    });
}

Resource Based (Keycloak Authorization Services)

import  KeycloakAdapter from 'keycloak-lambda-authorizer';

const keycloakJSON = ...; // read Keycloak.json

const keycloakAdapter = new KeycloakAdapter({
  keycloakJson: keycloakJSON,
});

export function authorizer(event, context, callback) {
    const requestContent = await keycloakAdapter.getAPIGateWayAdapter().validate(event, {
    resource: {
    name: 'SOME_RESOURCE',
    uri: 'RESOURCE_URI',
    matchingUri: true,
    },
   });
}

Configuration

Option structure:

 {
    keys: ClientJwtKeys,
    keycloakJson: keycloakJsonFunction
 }

Resource Structure:

{
    name?: string,
    uri?: string,
    owner?: string,
    type?: string,
    scope?: string,
    matchingUri?: boolean,
    deep?: boolean,
    first?: number,
    max?: number,
}
  • name : unique name of resource
  • uri : URIs which are protected by resource.
  • Owner : Owner of resource
  • type : Type of Resource
  • scope : The scope associated with this resource.
  • matchingUri : matching Uri

Keycloak Admin Console 2020-04-11 23-58-06

Change logger

import  KeycloakAdapter from 'keycloak-lambda-authorizer';
import winston from 'winston';

const keycloakJSON = ...; // read Keycloak.json

const keycloakAdapter = new KeycloakAdapter({
  keycloakJson: keycloakJSON,
  logger:winston
});

ExpressJS middleware

import fs from 'fs';
import express from 'express';
import KeycloakAdapter from 'keycloak-lambda-authorizer';

function getKeycloakJSON() {
  return JSON.parse(fs.readFileSync(`${__dirname}/keycloak.json`, 'utf8'));
}

const keycloakAdapter = new KeycloakAdapter({
  keycloakJson: getKeycloakJSON(),
});

const app = express();

app.get('/expressServiceApi', keycloakAdapter.getExpressMiddlewareAdapter().middleware({
  resource: {
    name: 'service-api',
  },
}),
async (request:any, response) => {
  response.json({
    message: `Hi ${request.jwt.payload.preferred_username}. Your function executed successfully!`,
  });
});

Get Service Account Token

  • ExpressJS
import fs from 'fs';
import express from 'express';
import KeycloakAdapter from 'keycloak-lambda-authorizer';

function getKeycloakJSON() {
  return JSON.parse(fs.readFileSync(`${__dirname}/keycloak.json`, 'utf8'));
}

const expressMiddlewareAdapter = new KeycloakAdapter({
  keycloakJson: getKeycloakJSON(),
}).getExpressMiddlewareAdapter();

const app = express();

app.get('/expressServiceApi', expressMiddlewareAdapter.middleware(
  {
    resource: {
      name: 'service-api',
    },
  },
),
async (request:any, response) => {
  const serviceJWT = await request.serviceAccountJWT();
 ...
});
  • AWS Lambda/Serverless or another cloud
import KeycloakAdapter from 'keycloak-lambda-authorizer';

const keycloakJson = ...;

const keycloakAdapter = new KeycloakAdapter({
  keycloakJson: keycloakJson,
});

async function getServiceAccountJWT(){
   return await keycloakAdapter.serviceAccountJWT();
}
...
const serviceAccountToken = await getServiceAccountJWT();
...
  • AWS Lambda/Serverless or another cloud with Signed JWT
import KeycloakAdapter from 'keycloak-lambda-authorizer';

const keycloakJson = ...;

const keycloakAdapter = new KeycloakAdapter({
  keycloakJson: keycloakJson,
  keys: {
      privateKey: {
        key: privateKey,
      },
      publicKey: {
        key: publicKey,
      },
    }
});

async function getServiceAccountJWT(){
   return await keycloakAdapter.serviceAccountJWT();
}
...

const serviceAccountToken = await getServiceAccountJWT();

Cache

Example of cache:

export class DefaultCache implements AdapterCache {
  async get(region: string, key: string): Promise<string | undefined> {
    ...
  }

  async put(region: string, key: string, value: any, ttl: number): Promise<void> {
    ...
  }
}

Cache Regions:

  • publicKey - Cache for storing Public Keys. (The time to live - 180 sec)
  • uma2-configuration - uma2-configuration link. example of link http://localhost:8090/auth/realms/lambda-authorizer/.well-known/uma2-configuration (The time to live - 180 sec)
  • client_credentials - Service Accounts Credential Cache (The time to live - 180 sec).
  • resource - Resources Cache (The time to live - 30 sec).
  • rpt - Resources Cache (The time to live - refresh token expiration time).

Change Cache:

import  KeycloakAdapter from 'keycloak-lambda-authorizer';

const keycloakJSON = ...; // read Keycloak.json

const keycloakAdapter = new KeycloakAdapter({
  keycloakJson: keycloakJSON,
  cache:newCache
});

Client Jwt Credential Type

- RSA Keys Structure

{
   "privateKey":{
      "key":"privateKey",
      "passphrase":"privateKey passphrase"
   },
   "publicKey":{
      "key":"publicKey"
   }
}
  • privateKey.key - RSA Private Key
  • privateKey.passphrase - word or phrase that protects private key
  • publicKey.key - RSA Public Key or Certificate

RSA keys generation example using openssl

openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -subj "/CN=<CLIENT-ID>" -keyout server.key -out server.crt

Create JWKS endpoint by AWS API Gateway

  • serverless.yaml
functions:
  cert:
    handler: handler.cert
    events:
      - http:
          path: cert
          method: GET
  • lambda function (handler.cert)
import KeycloakAdapter from 'keycloak-lambda-authorizer';

const keycloakJson = ...;

const keycloakAdapter = new KeycloakAdapter({
  keycloakJson: keycloakJson,
  keys: {
      privateKey: {
        key: privateKey,
      },
      publicKey: {
        key: publicKey,
      },
    }
});

  const jwksResponse = keycloakAdapter.getJWKS().json({
    key: publicKey,
  });
  callback(null, {
    statusCode: 200,
    body: JSON.stringify(jwksResponse),
  });
  • Keycloak Settings Keycloak Admin Console 2020-04-12 13-30-26

Create Api GateWay Authorizer function

import KeycloakAdapter from 'keycloak-lambda-authorizer';

const keycloakJson = ...;

const keycloakAdapter = new KeycloakAdapter({
  keycloakJson: keycloakJson
});

export async function authorizer(event, context, callback) {
   const requestContent = await keycloakAdapter.getAPIGateWayAdapter().validate(event, {
       resource: {
         name: 'LambdaResource',
         uri: 'LambdaResource123',
         matchingUri: true,
     },
   });
   return requestContent.token.payload;
}

Implementation For Custom Service or non amazon cloud

import KeycloakAdapter from 'keycloak-lambda-authorizer';

const keycloakJson = {
   "realm": "lambda-authorizer",
   "auth-server-url": "http://localhost:8090/auth",
   "ssl-required": "external",
   "resource": "lambda",
   "verify-token-audience": true,
   "credentials": {
     "secret": "772decbe-0151-4b08-8171-bec6d097293b"
   },
   "confidential-port": 0,
   "policy-enforcer": {}
}

const keycloakAdapter = new KeycloakAdapter({
  keycloakJson: keycloakJson
}).getDefaultAdapter();


async function handler(request,response) {
  const authorization = request.headers.Authorization;
  const match = authorization.match(/^Bearer (.*)$/);
  if (!match || match.length < 2) {
    throw new Error(`Invalid Authorization token - '${authorization}' does not match 'Bearer .*'`);
  }
  const jwtToken =  match[1];
  await keycloakAdapter.validate(jwtToken, {
                                          resource: {
                                            name: 'SOME_RESOURCE',
                                            uri: 'RESOURCE_URI',
                                            matchingUri: true,
                                          },
                                      });
...
}

Validate and Refresh Token

import KeycloakAdapter from 'keycloak-lambda-authorizer';

const keycloakJson = {
   "realm": "lambda-authorizer",
   "auth-server-url": "http://localhost:8090/auth",
   "ssl-required": "external",
   "resource": "lambda",
   "verify-token-audience": true,
   "credentials": {
     "secret": "772decbe-0151-4b08-8171-bec6d097293b"
   },
   "confidential-port": 0,
   "policy-enforcer": {}
}

const keycloakAdapter = new KeycloakAdapter({
  keycloakJson: keycloakJson
}).getDefaultAdapter();


async function handler(request,response) {
  let tokenJson:TokenJson = readCurrentToken();
  const authorization = {
                          resource: {
                            name: 'SOME_RESOURCE',
                            uri: 'RESOURCE_URI',
                            matchingUri: true,
                          },
                         }
  try{
    await keycloakAdapter.validate(tokenJson.access_token, authorization);
  } catch(e){
   tokenJson =  await keycloakAdapter.refreshToken(tokenJson, authorization);
   writeToken(tokenJson)
  }
...
}

Package Sidebar

Install

npm i keycloak-lambda-authorizer

Weekly Downloads

294

Version

1.0.10

License

Apache-2.0

Unpacked Size

2.56 MB

Total Files

127

Last publish

Collaborators

  • vzakharchenko