{"id":15068312,"url":"https://github.com/vzakharchenko/keycloak-lambda-authorizer","last_synced_at":"2025-04-10T16:40:18.710Z","repository":{"id":43172429,"uuid":"254093322","full_name":"vzakharchenko/keycloak-lambda-authorizer","owner":"vzakharchenko","description":"Keycloak adapter for Cloud","archived":false,"fork":false,"pushed_at":"2022-03-15T14:44:52.000Z","size":2731,"stargazers_count":38,"open_issues_count":2,"forks_count":16,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-24T14:21:22.871Z","etag":null,"topics":["api-gateway","authorization","aws-lambda","client-jwt","cloud","cross-realm","express","expressjs","jwt","keycloak","keycloak-adapter","lambda-authorizer","lambda-functions","multi-tenancy","multi-tenant","multi-tenant-infrastructure","oidc","serverless"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vzakharchenko.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"docs/securityRealm1.png","support":null}},"created_at":"2020-04-08T13:19:15.000Z","updated_at":"2023-12-15T15:15:42.000Z","dependencies_parsed_at":"2022-08-26T05:41:35.453Z","dependency_job_id":null,"html_url":"https://github.com/vzakharchenko/keycloak-lambda-authorizer","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vzakharchenko%2Fkeycloak-lambda-authorizer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vzakharchenko%2Fkeycloak-lambda-authorizer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vzakharchenko%2Fkeycloak-lambda-authorizer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vzakharchenko%2Fkeycloak-lambda-authorizer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vzakharchenko","download_url":"https://codeload.github.com/vzakharchenko/keycloak-lambda-authorizer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247717593,"owners_count":20984446,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-gateway","authorization","aws-lambda","client-jwt","cloud","cross-realm","express","expressjs","jwt","keycloak","keycloak-adapter","lambda-authorizer","lambda-functions","multi-tenancy","multi-tenant","multi-tenant-infrastructure","oidc","serverless"],"created_at":"2024-09-25T01:33:59.560Z","updated_at":"2025-04-10T16:40:18.689Z","avatar_url":"https://github.com/vzakharchenko.png","language":"TypeScript","readme":"- [![CircleCI](https://circleci.com/gh/vzakharchenko/keycloak-lambda-authorizer.svg?style=svg)](https://circleci.com/gh/vzakharchenko/keycloak-lambda-authorizer)\n- [![npm version](https://badge.fury.io/js/keycloak-lambda-authorizer.svg)](https://badge.fury.io/js/keycloak-lambda-authorizer)\n- [![Coverage Status](https://coveralls.io/repos/github/vzakharchenko/keycloak-lambda-authorizer/badge.svg?branch=master)](https://coveralls.io/github/vzakharchenko/keycloak-lambda-authorizer?branch=master)\n- [![Maintainability](https://api.codeclimate.com/v1/badges/0b56148967afc99a64df/maintainability)](https://codeclimate.com/github/vzakharchenko/keycloak-lambda-authorizer/maintainability)\n- [![Node.js 12.x, 14.x, 15.x, 17.x CI](https://github.com/vzakharchenko/keycloak-lambda-authorizer/actions/workflows/nodejs.yml/badge.svg)](https://github.com/vzakharchenko/keycloak-lambda-authorizer/actions/workflows/nodejs.yml)\n- [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=vzakharchenko_keycloak-lambda-authorizer\u0026metric=bugs)](https://sonarcloud.io/dashboard?id=vzakharchenko_keycloak-lambda-authorizer)\n- [![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=vzakharchenko_keycloak-lambda-authorizer\u0026metric=sqale_index)](https://sonarcloud.io/dashboard?id=vzakharchenko_keycloak-lambda-authorizer)\n- [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=vzakharchenko_keycloak-lambda-authorizer\u0026metric=security_rating)](https://sonarcloud.io/dashboard?id=vzakharchenko_keycloak-lambda-authorizer)\n\n\n# Description\nImplementation [Keycloak](https://www.keycloak.org/) adapter for Cloud\n## Features\n- supports AWS API Gateway\n- Resource based authorization ( [Keycloak Authorization Services](https://www.keycloak.org/docs/latest/authorization_services/) )\n- works with non amazon services.\n- [Service to Service communication](./examples/userToAdminAPI).\n- validate expiration of JWT token\n- validate JWS signature\n- supports \"clientId/secret\" and \"client-jwt\" credential types\n- Role based authorization\n- support MultiTenancy\n- [cross-realm authentication](https://github.com/vzakharchenko/keycloak-api-gateway/tree/master/examples/crossTenantReactJSExample)\n** Note: supporting Lambda@Edge moved to [https://github.com/vzakharchenko/keycloak-api-gateway](https://github.com/vzakharchenko/keycloak-api-gateway) **\n# Installation\n\n```\nnpm install keycloak-lambda-authorizer -S\n```\n# Examples\n - [Serverless example (Api gateway with lambda authorizer)](examples/keycloak-authorizer/README.md)\n - [Example of expressjs middleware](examples/express)\n - [Example of expressjs middleware with security resource scopes](examples/express-scopes)\n - [Example of calling a chain of micro services, where each service is protected by its secured client](examples/chain-service-calls)\n - [Example of calling the Admin API Using the regular User Permissions (Role or Resource)](examples/userToAdminAPI)\n - [CloudFront with Lambda:Edge example](https://github.com/vzakharchenko/keycloak-api-gateway/blob/master/examples/reactJSExample)\n - [CloudFront with portal authorization (switching between security realms)](https://github.com/vzakharchenko/keycloak-api-gateway/blob/master/examples/crossTenantReactJSExample)\n# How to use\n\n### Role Based\n```javascript\nimport  KeycloakAdapter from 'keycloak-lambda-authorizer';\n\nconst keycloakJSON = ...; // read Keycloak.json\n\nconst keycloakAdapter = new KeycloakAdapter({\n  keycloakJson: keycloakJSON,\n});\n\nexport async  function authorizer(event, context, callback) {\n\n    const requestContent = await keycloakAdapter.getAPIGateWayAdapter().validate(event, {\n     role: 'SOME_ROLE',\n    });\n}\n```\n### Client Role Based\n```javascript\nimport  KeycloakAdapter from 'keycloak-lambda-authorizer';\n\nconst keycloakJSON = ...; // read Keycloak.json\n\nconst keycloakAdapter = new KeycloakAdapter({\n  keycloakJson: keycloakJSON,\n});\n\nexport async  function authorizer(event, context, callback) {\n    const requestContent = await keycloakAdapter.getAPIGateWayAdapter().validate(event, {\n     clientRole:{role: 'SOME_ROLE', clientId: 'Client Name'}\n    });\n}\n```\n\n### Resource Based (Keycloak Authorization Services)\n```javascript\nimport  KeycloakAdapter from 'keycloak-lambda-authorizer';\n\nconst keycloakJSON = ...; // read Keycloak.json\n\nconst keycloakAdapter = new KeycloakAdapter({\n  keycloakJson: keycloakJSON,\n});\n\nexport function authorizer(event, context, callback) {\n    const requestContent = await keycloakAdapter.getAPIGateWayAdapter().validate(event, {\n    resource: {\n    name: 'SOME_RESOURCE',\n    uri: 'RESOURCE_URI',\n    matchingUri: true,\n    },\n   });\n}\n```\n\n# Configuration\n\n## Option structure:\n```javascript\n {\n    keys: ClientJwtKeys,\n    keycloakJson: keycloakJsonFunction\n }\n```\n## Resource Structure:\n\n```javascript\n{\n    name?: string,\n    uri?: string,\n    owner?: string,\n    type?: string,\n    scope?: string,\n    matchingUri?: boolean,\n    deep?: boolean,\n    first?: number,\n    max?: number,\n}\n```\n- **name** : unique name of resource\n- **uri** :  URIs which are protected by resource.\n- **Owner** : Owner of resource\n- **type** : Type of Resource\n- **scope** : The scope associated with this resource.\n- **matchingUri** : matching Uri\n\n![Keycloak Admin Console 2020-04-11 23-58-06](docs/Keycloak%20Admin%20Console%202020-04-11%2023-58-06.png)\n\n## Change logger\n```javascript\nimport  KeycloakAdapter from 'keycloak-lambda-authorizer';\nimport winston from 'winston';\n\nconst keycloakJSON = ...; // read Keycloak.json\n\nconst keycloakAdapter = new KeycloakAdapter({\n  keycloakJson: keycloakJSON,\n  logger:winston\n});\n\n```\n\n## ExpressJS middleware\n\n```js\nimport fs from 'fs';\nimport express from 'express';\nimport KeycloakAdapter from 'keycloak-lambda-authorizer';\n\nfunction getKeycloakJSON() {\n  return JSON.parse(fs.readFileSync(`${__dirname}/keycloak.json`, 'utf8'));\n}\n\nconst keycloakAdapter = new KeycloakAdapter({\n  keycloakJson: getKeycloakJSON(),\n});\n\nconst app = express();\n\napp.get('/expressServiceApi', keycloakAdapter.getExpressMiddlewareAdapter().middleware({\n  resource: {\n    name: 'service-api',\n  },\n}),\nasync (request:any, response) =\u003e {\n  response.json({\n    message: `Hi ${request.jwt.payload.preferred_username}. Your function executed successfully!`,\n  });\n});\n```\n\n## Get Service Account Token\n - ExpressJS\n```js\n\nimport fs from 'fs';\nimport express from 'express';\nimport KeycloakAdapter from 'keycloak-lambda-authorizer';\n\nfunction getKeycloakJSON() {\n  return JSON.parse(fs.readFileSync(`${__dirname}/keycloak.json`, 'utf8'));\n}\n\nconst expressMiddlewareAdapter = new KeycloakAdapter({\n  keycloakJson: getKeycloakJSON(),\n}).getExpressMiddlewareAdapter();\n\nconst app = express();\n\napp.get('/expressServiceApi', expressMiddlewareAdapter.middleware(\n  {\n    resource: {\n      name: 'service-api',\n    },\n  },\n),\nasync (request:any, response) =\u003e {\n  const serviceJWT = await request.serviceAccountJWT();\n ...\n});\n```\n- AWS Lambda/Serverless or another cloud\n\n```js\nimport KeycloakAdapter from 'keycloak-lambda-authorizer';\n\nconst keycloakJson = ...;\n\nconst keycloakAdapter = new KeycloakAdapter({\n  keycloakJson: keycloakJson,\n});\n\nasync function getServiceAccountJWT(){\n   return await keycloakAdapter.serviceAccountJWT();\n}\n...\nconst serviceAccountToken = await getServiceAccountJWT();\n...\n```\n\n- AWS Lambda/Serverless or another cloud with Signed JWT\n\n```js\nimport KeycloakAdapter from 'keycloak-lambda-authorizer';\n\nconst keycloakJson = ...;\n\nconst keycloakAdapter = new KeycloakAdapter({\n  keycloakJson: keycloakJson,\n  keys: {\n      privateKey: {\n        key: privateKey,\n      },\n      publicKey: {\n        key: publicKey,\n      },\n    }\n});\n\nasync function getServiceAccountJWT(){\n   return await keycloakAdapter.serviceAccountJWT();\n}\n...\n\nconst serviceAccountToken = await getServiceAccountJWT();\n\n```\n\n## Cache\nExample of cache:\n```javascript\n\nexport class DefaultCache implements AdapterCache {\n  async get(region: string, key: string): Promise\u003cstring | undefined\u003e {\n    ...\n  }\n\n  async put(region: string, key: string, value: any, ttl: number): Promise\u003cvoid\u003e {\n    ...\n  }\n}\n\n```\n### Cache Regions:\n\n- **publicKey** - Cache for storing Public Keys. (The time to live - 180 sec)\n- **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)\n- **client_credentials** - Service Accounts Credential Cache (The time to live - 180 sec).\n- **resource** - Resources Cache (The time to live - 30 sec).\n- **rpt** - Resources Cache (The time to live - refresh token expiration time).\n\n### Change Cache:\n\n```javascript\n\nimport  KeycloakAdapter from 'keycloak-lambda-authorizer';\n\nconst keycloakJSON = ...; // read Keycloak.json\n\nconst keycloakAdapter = new KeycloakAdapter({\n  keycloakJson: keycloakJSON,\n  cache:newCache\n});\n```\n\n## Client Jwt Credential Type\n\n###  - RSA Keys Structure\n\n```javascript\n{\n   \"privateKey\":{\n      \"key\":\"privateKey\",\n      \"passphrase\":\"privateKey passphrase\"\n   },\n   \"publicKey\":{\n      \"key\":\"publicKey\"\n   }\n}\n```\n- privateKey.**key** - RSA Private Key\n- privateKey.**passphrase** - word or phrase that protects private key\n- publicKey.**key** - RSA Public Key or Certificate\n\n###  RSA keys generation example using openssl\n\n```bash\nopenssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -subj \"/CN=\u003cCLIENT-ID\u003e\" -keyout server.key -out server.crt\n```\n\n### Create JWKS endpoint by AWS API Gateway\n\n - serverless.yaml\n```yaml\nfunctions:\n  cert:\n    handler: handler.cert\n    events:\n      - http:\n          path: cert\n          method: GET\n```\n - lambda function (handler.cert)\n```javascript\nimport KeycloakAdapter from 'keycloak-lambda-authorizer';\n\nconst keycloakJson = ...;\n\nconst keycloakAdapter = new KeycloakAdapter({\n  keycloakJson: keycloakJson,\n  keys: {\n      privateKey: {\n        key: privateKey,\n      },\n      publicKey: {\n        key: publicKey,\n      },\n    }\n});\n\n  const jwksResponse = keycloakAdapter.getJWKS().json({\n    key: publicKey,\n  });\n  callback(null, {\n    statusCode: 200,\n    body: JSON.stringify(jwksResponse),\n  });\n```\n - Keycloak Settings\n![Keycloak Admin Console 2020-04-12 13-30-26](docs/Keycloak%20Admin%20Console%202020-04-12%2013-30-26.png)\n\n\n### Create Api GateWay Authorizer function\n\n```javascript\nimport KeycloakAdapter from 'keycloak-lambda-authorizer';\n\nconst keycloakJson = ...;\n\nconst keycloakAdapter = new KeycloakAdapter({\n  keycloakJson: keycloakJson\n});\n\nexport async function authorizer(event, context, callback) {\n   const requestContent = await keycloakAdapter.getAPIGateWayAdapter().validate(event, {\n       resource: {\n         name: 'LambdaResource',\n         uri: 'LambdaResource123',\n         matchingUri: true,\n     },\n   });\n   return requestContent.token.payload;\n}\n```\n\n# Implementation For Custom Service or non amazon cloud\n\n```javascript\nimport KeycloakAdapter from 'keycloak-lambda-authorizer';\n\nconst keycloakJson = {\n   \"realm\": \"lambda-authorizer\",\n   \"auth-server-url\": \"http://localhost:8090/auth\",\n   \"ssl-required\": \"external\",\n   \"resource\": \"lambda\",\n   \"verify-token-audience\": true,\n   \"credentials\": {\n     \"secret\": \"772decbe-0151-4b08-8171-bec6d097293b\"\n   },\n   \"confidential-port\": 0,\n   \"policy-enforcer\": {}\n}\n\nconst keycloakAdapter = new KeycloakAdapter({\n  keycloakJson: keycloakJson\n}).getDefaultAdapter();\n\n\nasync function handler(request,response) {\n  const authorization = request.headers.Authorization;\n  const match = authorization.match(/^Bearer (.*)$/);\n  if (!match || match.length \u003c 2) {\n    throw new Error(`Invalid Authorization token - '${authorization}' does not match 'Bearer .*'`);\n  }\n  const jwtToken =  match[1];\n  await keycloakAdapter.validate(jwtToken, {\n                                          resource: {\n                                            name: 'SOME_RESOURCE',\n                                            uri: 'RESOURCE_URI',\n                                            matchingUri: true,\n                                          },\n                                      });\n...\n}\n```\n# Validate and Refresh Token\n\n```javascript\nimport KeycloakAdapter from 'keycloak-lambda-authorizer';\n\nconst keycloakJson = {\n   \"realm\": \"lambda-authorizer\",\n   \"auth-server-url\": \"http://localhost:8090/auth\",\n   \"ssl-required\": \"external\",\n   \"resource\": \"lambda\",\n   \"verify-token-audience\": true,\n   \"credentials\": {\n     \"secret\": \"772decbe-0151-4b08-8171-bec6d097293b\"\n   },\n   \"confidential-port\": 0,\n   \"policy-enforcer\": {}\n}\n\nconst keycloakAdapter = new KeycloakAdapter({\n  keycloakJson: keycloakJson\n}).getDefaultAdapter();\n\n\nasync function handler(request,response) {\n  let tokenJson:TokenJson = readCurrentToken();\n  const authorization = {\n                          resource: {\n                            name: 'SOME_RESOURCE',\n                            uri: 'RESOURCE_URI',\n                            matchingUri: true,\n                          },\n                         }\n  try{\n    await keycloakAdapter.validate(tokenJson.access_token, authorization);\n  } catch(e){\n   tokenJson =  await keycloakAdapter.refreshToken(tokenJson, authorization);\n   writeToken(tokenJson)\n  }\n...\n}\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvzakharchenko%2Fkeycloak-lambda-authorizer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvzakharchenko%2Fkeycloak-lambda-authorizer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvzakharchenko%2Fkeycloak-lambda-authorizer/lists"}