├── .dockerignore ├── .vscode └── settings.json ├── .prettierrc ├── nest-cli.json ├── tsconfig.build.json ├── src ├── auth │ ├── jwt-auth.guard.ts │ ├── auth.module.ts │ ├── jwt.strategy.ts │ ├── auth.controller.ts │ ├── auth.service.ts │ └── keycloak.service.ts ├── main.ts └── app.module.ts ├── test ├── jest-e2e.json └── app.e2e-spec.ts ├── .env.example ├── Dockerfile ├── .gitignore ├── tsconfig.json ├── .eslintrc.js ├── client.http ├── docker-compose.yaml ├── LICENSE ├── package.json ├── nestjs-keycloak.postman_collection.json ├── README.md └── nestjs-keycloak-realm.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env 4 | .vscode 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /src/auth/jwt-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | @Injectable() 5 | export class JwtAuthGuard extends AuthGuard('jwt') {} 6 | -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule); 6 | await app.listen(3000); 7 | } 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | KEYCLOAK_BASE_URL="http://localhost:8080" 2 | KEYCLOAK_REALM="nestjs-keycloak" 3 | KEYCLOAK_REALM_RSA_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n\n-----END PUBLIC KEY-----" 4 | KEYCLOAK_CLIENT_ID="nestjs-keycloak" 5 | KEYCLOAK_CLIENT_SECRET="" 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine3.14 AS build 2 | 3 | WORKDIR /build 4 | 5 | COPY [ "package.json", "package-lock.json", "./" ] 6 | 7 | RUN npm ci 8 | 9 | COPY . . 10 | 11 | RUN npm run build 12 | 13 | FROM node:alpine3.14 AS runtime 14 | 15 | USER node 16 | 17 | WORKDIR /usr/src/app 18 | 19 | COPY --from=build /build/ ./ 20 | 21 | ENV NODE_PORT=3000 22 | EXPOSE ${NODE_PORT} 23 | 24 | CMD [ "npm", "run", "start:prod" ] -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule } from '@nestjs/axios'; 2 | import { Module } from '@nestjs/common'; 3 | import { ConfigModule } from '@nestjs/config'; 4 | 5 | import { AuthModule } from './auth/auth.module'; 6 | 7 | @Module({ 8 | imports: [ 9 | ConfigModule.forRoot({ 10 | isGlobal: true, 11 | }), 12 | AuthModule, 13 | ], 14 | controllers: [], 15 | providers: [], 16 | }) 17 | export class AppModule {} 18 | -------------------------------------------------------------------------------- /src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule } from '@nestjs/axios'; 2 | import { Module } from '@nestjs/common'; 3 | 4 | import { AuthController } from './auth.controller'; 5 | import { AuthService } from './auth.service'; 6 | import { JwtStrategy } from './jwt.strategy'; 7 | import { KeycloakService } from './keycloak.service'; 8 | 9 | @Module({ 10 | imports: [HttpModule], 11 | controllers: [AuthController], 12 | providers: [AuthService, KeycloakService, JwtStrategy], 13 | }) 14 | export class AuthModule {} 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json 36 | 37 | .env 38 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/recommended', 10 | 'plugin:prettier/recommended', 11 | ], 12 | root: true, 13 | env: { 14 | node: true, 15 | jest: true, 16 | }, 17 | ignorePatterns: ['.eslintrc.js'], 18 | rules: { 19 | '@typescript-eslint/interface-name-prefix': 'off', 20 | '@typescript-eslint/explicit-function-return-type': 'off', 21 | '@typescript-eslint/explicit-module-boundary-types': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/auth/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { ExtractJwt, Strategy } from 'passport-jwt'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { Injectable } from '@nestjs/common'; 4 | import { ConfigService } from '@nestjs/config'; 5 | 6 | @Injectable() 7 | export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { 8 | constructor(configService: ConfigService) { 9 | super({ 10 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 11 | ignoreExpiration: false, 12 | secretOrKey: configService.get('KEYCLOAK_REALM_RSA_PUBLIC_KEY'), 13 | }); 14 | } 15 | 16 | async validate(payload: any) { 17 | /** 18 | * This can be obtained via req.user in the Controllers 19 | * This is where we validate that the user is valid and delimit the payload returned to req.user 20 | */ 21 | return { userId: payload.sub }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client.http: -------------------------------------------------------------------------------- 1 | @base_url=http://localhost:3000 2 | 3 | # @name login 4 | POST {{base_url}}/auth/login HTTP/1.1 5 | Content-Type: application/json 6 | 7 | { 8 | "username": "test@test.com", 9 | "password": "test" 10 | } 11 | 12 | ### 13 | 14 | @access_token = {{login.response.body.access_token}} 15 | @refresh_token = {{login.response.body.refresh_token}} 16 | 17 | ### 18 | 19 | # @name me 20 | GET {{base_url}}/auth/me HTTP/1.1 21 | Authorization: Bearer {{access_token}} 22 | 23 | ### 24 | 25 | # @name refresh 26 | POST {{base_url}}/auth/refresh HTTP/1.1 27 | Content-Type: application/json 28 | 29 | { 30 | "refresh_token": "{{refresh_token}}" 31 | } 32 | 33 | ### 34 | 35 | @access_token = {{login.response.body.access_token}} 36 | @refresh_token = {{login.response.body.refresh_token}} 37 | 38 | ### 39 | 40 | # @name logout 41 | POST {{base_url}}/auth/logout HTTP/1.1 42 | Content-Type: application/json 43 | 44 | { 45 | "refresh_token": "{{refresh_token}}" 46 | } 47 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | services: 4 | keycloak: 5 | image: jboss/keycloak:15.0.2 6 | ports: 7 | - 8080:8080 8 | environment: 9 | KEYCLOAK_USER: admin 10 | KEYCLOAK_PASSWORD: admin 11 | DB_VENDOR: h2 12 | KEYCLOAK_IMPORT: /tmp/nestjs-keycloak-realm.json 13 | volumes: 14 | - ./nestjs-keycloak-realm.json:/tmp/nestjs-keycloak-realm.json 15 | 16 | app: 17 | build: . 18 | command: npm run start:dev 19 | volumes: 20 | - .:/usr/src/app 21 | - node_modules:/usr/src/app/node_modules 22 | ports: 23 | - 3000:3000 24 | depends_on: 25 | - keycloak 26 | environment: 27 | NODE_ENV: development 28 | KEYCLOAK_BASE_URL: 'http://keycloak:8080' 29 | KEYCLOAK_REALM: ${KEYCLOAK_REALM} 30 | KEYCLOAK_REALM_RSA_PUBLIC_KEY: ${KEYCLOAK_REALM_RSA_PUBLIC_KEY} 31 | KEYCLOAK_CLIENT_ID: ${KEYCLOAK_CLIENT_ID} 32 | KEYCLOAK_CLIENT_SECRET: ${KEYCLOAK_CLIENT_SECRET} 33 | 34 | volumes: 35 | node_modules: 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 William Queiroz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { HttpCode, HttpStatus } from '@nestjs/common'; 2 | import { 3 | Body, 4 | Request, 5 | Controller, 6 | Get, 7 | Post, 8 | UseGuards, 9 | } from '@nestjs/common'; 10 | import { AuthService } from './auth.service'; 11 | import { JwtAuthGuard } from './jwt-auth.guard'; 12 | 13 | type LoginRequestBody = { 14 | username: string; 15 | password: string; 16 | }; 17 | 18 | type RefreshTokenRequestBody = { 19 | refresh_token: string; 20 | }; 21 | 22 | type LogoutRequestBody = { 23 | refresh_token: string; 24 | }; 25 | 26 | @Controller('/auth') 27 | export class AuthController { 28 | constructor(private readonly authService: AuthService) {} 29 | 30 | @Post('/login') 31 | @HttpCode(HttpStatus.OK) 32 | login(@Body() body: LoginRequestBody) { 33 | const { username, password } = body; 34 | 35 | return this.authService.login(username, password); 36 | } 37 | 38 | @UseGuards(JwtAuthGuard) 39 | @Get('/me') 40 | getProfile(@Request() req: Request) { 41 | const { authorization } = req.headers as any; 42 | 43 | const [, accessToken] = authorization.split(' '); 44 | 45 | return this.authService.getProfile(accessToken); 46 | } 47 | 48 | @Post('/refresh') 49 | @HttpCode(HttpStatus.OK) 50 | refreshToken(@Body() body: RefreshTokenRequestBody) { 51 | const { refresh_token: refreshToken } = body; 52 | 53 | return this.authService.refreshToken(refreshToken); 54 | } 55 | 56 | @Post('/logout') 57 | @HttpCode(HttpStatus.OK) 58 | async logout(@Body() body: LogoutRequestBody) { 59 | const { refresh_token: refreshToken } = body; 60 | 61 | await this.authService.logout(refreshToken); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger, UnauthorizedException } from '@nestjs/common'; 2 | 3 | import { KeycloakService } from './keycloak.service'; 4 | 5 | type LoginResponse = { 6 | access_token: string; 7 | refresh_token: string; 8 | expires_in: number; 9 | refresh_expires_in: number; 10 | }; 11 | 12 | @Injectable() 13 | export class AuthService { 14 | private readonly logger = new Logger(AuthService.name); 15 | 16 | constructor(private readonly keycloakService: KeycloakService) {} 17 | 18 | async login(username: string, password: string): Promise { 19 | const { access_token, expires_in, refresh_token, refresh_expires_in } = 20 | await this.keycloakService.login(username, password).catch(() => { 21 | throw new UnauthorizedException(); 22 | }); 23 | 24 | return { 25 | access_token, 26 | refresh_token, 27 | expires_in, 28 | refresh_expires_in, 29 | }; 30 | } 31 | 32 | async getProfile(accessToken: string) { 33 | this.logger.log('Getting user profile...'); 34 | 35 | return this.keycloakService.getUserInfo(accessToken).catch(() => { 36 | throw new UnauthorizedException(); 37 | }); 38 | } 39 | 40 | async refreshToken(refreshToken: string): Promise { 41 | const { access_token, expires_in, refresh_token, refresh_expires_in } = 42 | await this.keycloakService.refreshToken(refreshToken).catch(() => { 43 | throw new UnauthorizedException(); 44 | }); 45 | 46 | return { 47 | access_token, 48 | refresh_token, 49 | expires_in, 50 | refresh_expires_in, 51 | }; 52 | } 53 | 54 | async logout(refreshToken: string) { 55 | await this.keycloakService.logout(refreshToken).catch(() => { 56 | throw new UnauthorizedException(); 57 | }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestjs-keycloak", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@nestjs/axios": "0.0.1", 25 | "@nestjs/common": "^8.0.0", 26 | "@nestjs/config": "^1.0.1", 27 | "@nestjs/core": "^8.0.0", 28 | "@nestjs/jwt": "^8.0.0", 29 | "@nestjs/passport": "^8.0.1", 30 | "@nestjs/platform-express": "^8.0.0", 31 | "passport": "^0.4.1", 32 | "passport-jwt": "^4.0.0", 33 | "reflect-metadata": "^0.1.13", 34 | "rimraf": "^3.0.2", 35 | "rxjs": "^7.2.0" 36 | }, 37 | "devDependencies": { 38 | "@nestjs/cli": "^8.0.0", 39 | "@nestjs/schematics": "^8.0.0", 40 | "@nestjs/testing": "^8.0.0", 41 | "@types/express": "^4.17.13", 42 | "@types/jest": "^27.0.1", 43 | "@types/node": "^16.0.0", 44 | "@types/passport-jwt": "^3.0.6", 45 | "@types/supertest": "^2.0.11", 46 | "@typescript-eslint/eslint-plugin": "^4.28.2", 47 | "@typescript-eslint/parser": "^4.28.2", 48 | "eslint": "^7.30.0", 49 | "eslint-config-prettier": "^8.3.0", 50 | "eslint-plugin-prettier": "^3.4.0", 51 | "jest": "^27.0.6", 52 | "prettier": "^2.3.2", 53 | "supertest": "^6.1.3", 54 | "ts-jest": "^27.0.3", 55 | "ts-loader": "^9.2.3", 56 | "ts-node": "^10.0.0", 57 | "tsconfig-paths": "^3.10.1", 58 | "typescript": "^4.3.5" 59 | }, 60 | "jest": { 61 | "moduleFileExtensions": [ 62 | "js", 63 | "json", 64 | "ts" 65 | ], 66 | "rootDir": "src", 67 | "testRegex": ".*\\.spec\\.ts$", 68 | "transform": { 69 | "^.+\\.(t|j)s$": "ts-jest" 70 | }, 71 | "collectCoverageFrom": [ 72 | "**/*.(t|j)s" 73 | ], 74 | "coverageDirectory": "../coverage", 75 | "testEnvironment": "node" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/auth/keycloak.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpService } from '@nestjs/axios'; 2 | import { Injectable } from '@nestjs/common'; 3 | import { ConfigService } from '@nestjs/config'; 4 | import { firstValueFrom } from 'rxjs'; 5 | 6 | type LoginResponse = { 7 | access_token: string; 8 | scope: string; 9 | refresh_token: string; 10 | token_type: string; 11 | session_state: string; 12 | 'not-before-policy': number; 13 | refresh_expires_in: number; 14 | expires_in: number; 15 | }; 16 | 17 | type UserInfoResponse = { 18 | sub: string; 19 | email_verified: boolean; 20 | preferred_username: string; 21 | }; 22 | 23 | @Injectable() 24 | export class KeycloakService { 25 | private baseURL: string; 26 | private realm: string; 27 | private clientId: string; 28 | private clientSecret: string; 29 | 30 | constructor( 31 | private readonly configService: ConfigService, 32 | private readonly httpService: HttpService, 33 | ) { 34 | this.baseURL = this.configService.get('KEYCLOAK_BASE_URL'); 35 | this.realm = this.configService.get('KEYCLOAK_REALM'); 36 | this.clientId = this.configService.get('KEYCLOAK_CLIENT_ID'); 37 | this.clientSecret = this.configService.get('KEYCLOAK_CLIENT_SECRET'); 38 | } 39 | 40 | async login(username: string, password: string): Promise { 41 | const { data } = await firstValueFrom( 42 | this.httpService.post( 43 | `${this.baseURL}/auth/realms/${this.realm}/protocol/openid-connect/token`, 44 | new URLSearchParams({ 45 | client_id: this.clientId, 46 | client_secret: this.clientSecret, 47 | grant_type: 'password', 48 | username, 49 | password, 50 | }), 51 | ), 52 | ); 53 | 54 | return data; 55 | } 56 | 57 | async getUserInfo(accessToken: string): Promise { 58 | const { data } = await firstValueFrom( 59 | this.httpService.get( 60 | `${this.baseURL}/auth/realms/${this.realm}/protocol/openid-connect/userinfo`, 61 | { 62 | headers: { 63 | Authorization: `Bearer ${accessToken}`, 64 | }, 65 | }, 66 | ), 67 | ); 68 | 69 | return data; 70 | } 71 | 72 | async refreshToken(refreshToken: string): Promise { 73 | const { data } = await firstValueFrom( 74 | this.httpService.post( 75 | `${this.baseURL}/auth/realms/${this.realm}/protocol/openid-connect/token`, 76 | new URLSearchParams({ 77 | client_id: this.clientId, 78 | client_secret: this.clientSecret, 79 | grant_type: 'refresh_token', 80 | refresh_token: refreshToken, 81 | }), 82 | ), 83 | ); 84 | 85 | return data; 86 | } 87 | 88 | async logout(refreshToken: string) { 89 | await firstValueFrom( 90 | this.httpService.post( 91 | `${this.baseURL}/auth/realms/${this.realm}/protocol/openid-connect/logout`, 92 | new URLSearchParams({ 93 | client_id: this.clientId, 94 | client_secret: this.clientSecret, 95 | refresh_token: refreshToken, 96 | }), 97 | ), 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /nestjs-keycloak.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "f23cb2d4-c352-4ec1-93ba-9400717649e4", 4 | "name": "nestjs-keycloak", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "01. Login", 10 | "event": [ 11 | { 12 | "listen": "test", 13 | "script": { 14 | "exec": [ 15 | "const { access_token, refresh_token } = JSON.parse(responseBody);", 16 | "", 17 | "if (access_token) pm.collectionVariables.set(\"access_token\", access_token);", 18 | "if (refresh_token) pm.collectionVariables.set(\"refresh_token\", refresh_token);", 19 | "" 20 | ], 21 | "type": "text/javascript" 22 | } 23 | } 24 | ], 25 | "request": { 26 | "method": "POST", 27 | "header": [], 28 | "body": { 29 | "mode": "raw", 30 | "raw": "{\n \"username\": \"test\",\n \"password\": \"test\"\n}", 31 | "options": { 32 | "raw": { 33 | "language": "json" 34 | } 35 | } 36 | }, 37 | "url": { 38 | "raw": ":3000/auth/login", 39 | "port": "3000", 40 | "path": [ 41 | "auth", 42 | "login" 43 | ] 44 | } 45 | }, 46 | "response": [] 47 | }, 48 | { 49 | "name": "02. Me", 50 | "request": { 51 | "auth": { 52 | "type": "bearer", 53 | "bearer": [ 54 | { 55 | "key": "token", 56 | "value": "{{access_token}}", 57 | "type": "string" 58 | } 59 | ] 60 | }, 61 | "method": "GET", 62 | "header": [], 63 | "url": { 64 | "raw": ":3000/auth/me", 65 | "port": "3000", 66 | "path": [ 67 | "auth", 68 | "me" 69 | ] 70 | } 71 | }, 72 | "response": [] 73 | }, 74 | { 75 | "name": "03. Refresh", 76 | "event": [ 77 | { 78 | "listen": "test", 79 | "script": { 80 | "exec": [ 81 | "const { access_token, refresh_token } = JSON.parse(responseBody);", 82 | "", 83 | "if (access_token) pm.collectionVariables.set(\"access_token\", access_token);", 84 | "if (refresh_token) pm.collectionVariables.set(\"refresh_token\", refresh_token);", 85 | "" 86 | ], 87 | "type": "text/javascript" 88 | } 89 | } 90 | ], 91 | "request": { 92 | "method": "POST", 93 | "header": [], 94 | "body": { 95 | "mode": "raw", 96 | "raw": "{\n \"refresh_token\": \"{{refresh_token}}\"\n}", 97 | "options": { 98 | "raw": { 99 | "language": "json" 100 | } 101 | } 102 | }, 103 | "url": { 104 | "raw": ":3000/auth/refresh", 105 | "port": "3000", 106 | "path": [ 107 | "auth", 108 | "refresh" 109 | ] 110 | } 111 | }, 112 | "response": [] 113 | }, 114 | { 115 | "name": "04. Logout", 116 | "event": [ 117 | { 118 | "listen": "test", 119 | "script": { 120 | "exec": [ 121 | "" 122 | ], 123 | "type": "text/javascript" 124 | } 125 | } 126 | ], 127 | "request": { 128 | "method": "POST", 129 | "header": [], 130 | "body": { 131 | "mode": "raw", 132 | "raw": "{\n \"refresh_token\": \"{{refresh_token}}\"\n}", 133 | "options": { 134 | "raw": { 135 | "language": "json" 136 | } 137 | } 138 | }, 139 | "url": { 140 | "raw": ":3000/auth/logout", 141 | "port": "3000", 142 | "path": [ 143 | "auth", 144 | "logout" 145 | ] 146 | } 147 | }, 148 | "response": [] 149 | } 150 | ], 151 | "variable": [ 152 | { 153 | "key": "access_token", 154 | "value": "" 155 | }, 156 | { 157 | "key": "refresh_token", 158 | "value": "" 159 | } 160 | ] 161 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wnqueiroz/nestjs-keycloak 2 | 3 | Project using [NestJS](https://nestjs.com/) version 8 to implement an authentication application with [Keycloak](https://www.keycloak.org/) integration! 🚀 4 | 5 | ## Table Of Contents 6 | 7 | - [Overview](#overview) 8 | - [Pre-requisites](#pre-requisites) 9 | - [Up and Running!](#up-and-running) 10 | - [Keycloak](#keycloak) 11 | - [Creating the environment variables file](#creating-the-environment-variables-file) 12 | - [Finishing Keycloak settings](#finishing-keycloak-settings) 13 | - [Getting the Realm's Public RSA Key](#getting-the-realms-public-rsa-key) 14 | - [Getting the Client Secret](#getting-the-client-secret) 15 | - [Creating the test user](#creating-the-test-user) 16 | - [NestJS](#nestjs) 17 | 18 | ## Overview 19 | 20 | The API implements 4 basic endpoints that can exist in an authentication mechanism, namely: 21 | 22 | - `POST /auth/login`: To log in properly, 23 | - `GET /auth/me`: To collect user information (requires login to be performed); 24 | - `POST /auth/refresh`: To generate a new access token (without needing to enter the credentials again); 25 | - `POST /auth/logout`: To invalidate the user's session on Keycloak (and also the tokens generated previously); 26 | 27 | Our Keycloak has a simple configuration, with a client called `nestjs-keycloak` (of the confidential type, so we can have a **Client ID** and a **Client Secret**). 28 | 29 | We will be using the [OpenID Connect 1.0 specification](https://openid.net/specs/openid-connect-core-1_0.html) to integrate with Keycloak. 30 | 31 | ## Pre-requisites 32 | 33 | In this walkthrough it's assumed that you have the tools installed: 34 | 35 | - [Docker](https://www.docker.com/get-started): 20.10.11 or higher. 36 | - [docker-compose](https://docs.docker.com/compose/install/): 1.29.2 or higher. 37 | 38 | ## Up and Running! 39 | 40 | There is already a `docker-compose.yaml` configured in this project. However, it is important to follow the tutorial below to obtain the correct environment variables and settings for the project to work. 41 | 42 | ### Keycloak 43 | 44 | Let's finish configuring Keycloak. Thanks to the [nestjs-keycloak-realm.json](./nestjs-keycloak-realm.json) file we won't need to waste so much time in this configuration. 45 | 46 | In the terminal, in the root of the project, execute the command to start the Keycloak: 47 | 48 | ```sh 49 | docker-compose up -d keycloak 50 | ``` 51 | 52 | Let's check the logs if Keycloak has started: 53 | 54 | ```sh 55 | docker-compose logs -f keycloak | grep -i "Admin console listening on" 56 | ``` 57 | 58 | Wait until a message appears on terminal output. 59 | 60 | #### Creating the environment variables file 61 | 62 | Copy the example environment variables file from this repository into a `.env` file at the root of the project: 63 | 64 | ```sh 65 | cp .env.example .env 66 | ``` 67 | 68 | > This file is needed for us to run the API from the `docker-compose.yaml`. 69 | 70 | **Let's replace what's in `` with the information we got from our Keycloak.** 71 | 72 | #### Finishing Keycloak settings 73 | 74 | Access the address: http://localhost:8080. You will see the Keycloak home screen. 75 | 76 | Go to **Administration Console**, and log in with the credentials: 77 | 78 | - Username or email: admin 79 | - Password: admin 80 | 81 | You will directly access the Realm that has been configured with the help of [nestjs-keycloak-realm.json](./nestjs-keycloak-realm.json). 82 | 83 | We now need: 84 | 85 | 1. Obtain the Realm's public RSA key; 86 | 1. Reset client secret of `nestjs-keycloak` client; 87 | 1. Create a test user for our Realm. 88 | 89 | #### Getting the Realm's Public RSA Key 90 | 91 | Access the address: http://localhost:8080/auth/admin/master/console/#/realms/nestjs-keycloak/keys. 92 | 93 | In the RSA key with the `rsa-generated` provider, click **Public Key**. 94 | 95 | Copy the displayed content and paste it into the `.env` file we created earlier, replacing the value `` with the key obtained from the variable `KEYCLOAK_REALM_RSA_PUBLIC_KEY`. It will look like: 96 | 97 | ```diff 98 | - KEYCLOAK_REALM_RSA_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n\n-----END PUBLIC KEY-----" 99 | + KEYCLOAK_REALM_RSA_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlOSbkRWDCFLl0dslyU1aYkACfos+wib22LHWTz9cgd+RBByS43wmxKNe90b5g6S0RMJvBUpcDnnUNMLXgP7EyfUluWriiUpyWXBLclhtWHz49QZYAOuR4T+4C2pmCfAkefDz5tbN+SQuRyZcJzZ/cLboKfKzwK7Nlud6NRvYMypOxmSNaDuQAKWH8BciT6ahpSFFgPWMBuGD5oz9DYqZKNNaXedmVLqL911EC0kH0J54AOjM4OKuo+sTUji6eJFCJDMynnJIJyTLFDRrhLM1aD+n77q0k59Bm/EwtP77tDT5uR0AWfhErdsZQV863TjBDcTEsxveEtOJmMF9L/6a8QIDAQAB\n-----END PUBLIC KEY-----" 100 | ``` 101 | 102 | #### Getting the Client Secret 103 | 104 | From the left bar, go to `Clients / nestjs-keycloak / Credentials` and click on **Regenerate Secret**. 105 | 106 | Copy the displayed content and paste it into the `.env` file we created earlier, replacing the `` value with the secret obtained from the `KEYCLOAK_CLIENT_SECRET` variable. It will look like: 107 | 108 | ```diff 109 | - KEYCLOAK_CLIENT_SECRET="" 110 | + KEYCLOAK_CLIENT_SECRET="2387cbc8-7bef-4f09-bd05-7f97ca4ae813" 111 | ``` 112 | 113 | #### Creating the test user 114 | 115 | Let's create a new user, click on the address: http://localhost:8080/auth/admin/master/console/#/create/user/nestjs-keycloak 116 | 117 | 1. Enter the information: 118 | 119 | - Username: test 120 | - Email: test@test.com 121 | 122 | > **Leave the other options as they are.** 123 | 124 | 2. Click `Save`. 125 | 126 | 3. Still on the newly created user screen, click on `Credentials` and enter the data: 127 | - Password: test 128 | - Password Confirmation: test 129 | - Temporary: OFF (switch to OFF) 130 | 131 | Click **Set Password** and confirm the operation. 132 | 133 | After that, we are able to launch our application! 🚀 🤘 134 | 135 | ### NestJS 136 | 137 | Let's launch our application. Run the command in the terminal: 138 | 139 | ```sh 140 | docker-compose up -d app 141 | ``` 142 | 143 | Wait for the application to build and run. 144 | 145 | Look at the logs with: 146 | 147 | ```sh 148 | docker-compose logs -f app 149 | ``` 150 | 151 | After identifying that the application has started, you can either "play" with the HTTP requests contained in the [client.http](./client.http) file or import the [Postman](https://www.postman.com/) Collection available in the [nestjs-keycloak.postman_collection.json](./nestjs-keycloak.postman_collection.json) file. 152 | -------------------------------------------------------------------------------- /nestjs-keycloak-realm.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "nestjs-keycloak", 3 | "realm": "nestjs-keycloak", 4 | "notBefore": 0, 5 | "defaultSignatureAlgorithm": "RS256", 6 | "revokeRefreshToken": false, 7 | "refreshTokenMaxReuse": 0, 8 | "accessTokenLifespan": 300, 9 | "accessTokenLifespanForImplicitFlow": 900, 10 | "ssoSessionIdleTimeout": 1800, 11 | "ssoSessionMaxLifespan": 36000, 12 | "ssoSessionIdleTimeoutRememberMe": 0, 13 | "ssoSessionMaxLifespanRememberMe": 0, 14 | "offlineSessionIdleTimeout": 2592000, 15 | "offlineSessionMaxLifespanEnabled": false, 16 | "offlineSessionMaxLifespan": 5184000, 17 | "clientSessionIdleTimeout": 0, 18 | "clientSessionMaxLifespan": 0, 19 | "clientOfflineSessionIdleTimeout": 0, 20 | "clientOfflineSessionMaxLifespan": 0, 21 | "accessCodeLifespan": 60, 22 | "accessCodeLifespanUserAction": 300, 23 | "accessCodeLifespanLogin": 1800, 24 | "actionTokenGeneratedByAdminLifespan": 43200, 25 | "actionTokenGeneratedByUserLifespan": 300, 26 | "oauth2DeviceCodeLifespan": 600, 27 | "oauth2DevicePollingInterval": 5, 28 | "enabled": true, 29 | "sslRequired": "external", 30 | "registrationAllowed": false, 31 | "registrationEmailAsUsername": false, 32 | "rememberMe": false, 33 | "verifyEmail": false, 34 | "loginWithEmailAllowed": true, 35 | "duplicateEmailsAllowed": false, 36 | "resetPasswordAllowed": false, 37 | "editUsernameAllowed": false, 38 | "bruteForceProtected": false, 39 | "permanentLockout": false, 40 | "maxFailureWaitSeconds": 900, 41 | "minimumQuickLoginWaitSeconds": 60, 42 | "waitIncrementSeconds": 60, 43 | "quickLoginCheckMilliSeconds": 1000, 44 | "maxDeltaTimeSeconds": 43200, 45 | "failureFactor": 30, 46 | "roles": { 47 | "realm": [ 48 | { 49 | "id": "2219368b-dc1a-425e-ba24-2210fe38c831", 50 | "name": "offline_access", 51 | "description": "${role_offline-access}", 52 | "composite": false, 53 | "clientRole": false, 54 | "containerId": "nestjs-keycloak", 55 | "attributes": {} 56 | }, 57 | { 58 | "id": "49764c24-8cd5-4ac2-b131-5cc5fbc50ace", 59 | "name": "default-roles-nestjs-keycloak", 60 | "description": "${role_default-roles}", 61 | "composite": true, 62 | "composites": { 63 | "realm": [ 64 | "offline_access", 65 | "uma_authorization" 66 | ], 67 | "client": { 68 | "account": [ 69 | "view-profile", 70 | "manage-account" 71 | ] 72 | } 73 | }, 74 | "clientRole": false, 75 | "containerId": "nestjs-keycloak", 76 | "attributes": {} 77 | }, 78 | { 79 | "id": "2f618d27-f2df-4572-8f5b-67f69be1f42b", 80 | "name": "uma_authorization", 81 | "description": "${role_uma_authorization}", 82 | "composite": false, 83 | "clientRole": false, 84 | "containerId": "nestjs-keycloak", 85 | "attributes": {} 86 | } 87 | ], 88 | "client": { 89 | "nestjs-keycloak": [], 90 | "realm-management": [ 91 | { 92 | "id": "fd9a62d1-ac87-40b2-96d7-e4c09acde727", 93 | "name": "impersonation", 94 | "description": "${role_impersonation}", 95 | "composite": false, 96 | "clientRole": true, 97 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 98 | "attributes": {} 99 | }, 100 | { 101 | "id": "c36c99b4-d144-4b59-aa5d-e1a639900bc8", 102 | "name": "query-realms", 103 | "description": "${role_query-realms}", 104 | "composite": false, 105 | "clientRole": true, 106 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 107 | "attributes": {} 108 | }, 109 | { 110 | "id": "4b88bd83-8aea-4d96-b596-a0dda46e1f24", 111 | "name": "view-identity-providers", 112 | "description": "${role_view-identity-providers}", 113 | "composite": false, 114 | "clientRole": true, 115 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 116 | "attributes": {} 117 | }, 118 | { 119 | "id": "6a9a8b54-5bfd-48b0-b789-fe32e8a49aff", 120 | "name": "query-clients", 121 | "description": "${role_query-clients}", 122 | "composite": false, 123 | "clientRole": true, 124 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 125 | "attributes": {} 126 | }, 127 | { 128 | "id": "b045ceaf-42d8-4f90-b7b2-5f31a411e572", 129 | "name": "manage-users", 130 | "description": "${role_manage-users}", 131 | "composite": false, 132 | "clientRole": true, 133 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 134 | "attributes": {} 135 | }, 136 | { 137 | "id": "89a12e8f-c44c-43e2-845b-36a9e13b6e53", 138 | "name": "view-realm", 139 | "description": "${role_view-realm}", 140 | "composite": false, 141 | "clientRole": true, 142 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 143 | "attributes": {} 144 | }, 145 | { 146 | "id": "cbace655-1b0a-4197-8840-0d4c4f32932c", 147 | "name": "manage-authorization", 148 | "description": "${role_manage-authorization}", 149 | "composite": false, 150 | "clientRole": true, 151 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 152 | "attributes": {} 153 | }, 154 | { 155 | "id": "4c19b423-4866-4539-a2f0-d437f7567c01", 156 | "name": "manage-clients", 157 | "description": "${role_manage-clients}", 158 | "composite": false, 159 | "clientRole": true, 160 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 161 | "attributes": {} 162 | }, 163 | { 164 | "id": "f8060a51-d0ed-4a99-9b77-e9a9b4ede80b", 165 | "name": "manage-realm", 166 | "description": "${role_manage-realm}", 167 | "composite": false, 168 | "clientRole": true, 169 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 170 | "attributes": {} 171 | }, 172 | { 173 | "id": "e62430a1-547d-4d79-bfbe-59da2c0df835", 174 | "name": "view-users", 175 | "description": "${role_view-users}", 176 | "composite": true, 177 | "composites": { 178 | "client": { 179 | "realm-management": [ 180 | "query-users", 181 | "query-groups" 182 | ] 183 | } 184 | }, 185 | "clientRole": true, 186 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 187 | "attributes": {} 188 | }, 189 | { 190 | "id": "ff121b4a-b945-4dd9-9723-489ed7b74490", 191 | "name": "realm-admin", 192 | "description": "${role_realm-admin}", 193 | "composite": true, 194 | "composites": { 195 | "client": { 196 | "realm-management": [ 197 | "query-realms", 198 | "impersonation", 199 | "view-identity-providers", 200 | "query-clients", 201 | "manage-users", 202 | "view-realm", 203 | "manage-clients", 204 | "manage-authorization", 205 | "manage-realm", 206 | "view-users", 207 | "view-clients", 208 | "manage-events", 209 | "create-client", 210 | "manage-identity-providers", 211 | "view-events", 212 | "query-users", 213 | "query-groups", 214 | "view-authorization" 215 | ] 216 | } 217 | }, 218 | "clientRole": true, 219 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 220 | "attributes": {} 221 | }, 222 | { 223 | "id": "24827d15-1641-4101-9be4-534983c27cf9", 224 | "name": "view-clients", 225 | "description": "${role_view-clients}", 226 | "composite": true, 227 | "composites": { 228 | "client": { 229 | "realm-management": [ 230 | "query-clients" 231 | ] 232 | } 233 | }, 234 | "clientRole": true, 235 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 236 | "attributes": {} 237 | }, 238 | { 239 | "id": "b71dde69-0988-47e7-ab56-8d0589110f4e", 240 | "name": "manage-events", 241 | "description": "${role_manage-events}", 242 | "composite": false, 243 | "clientRole": true, 244 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 245 | "attributes": {} 246 | }, 247 | { 248 | "id": "324f4f43-4994-4d4e-96d7-d096af2ff95f", 249 | "name": "create-client", 250 | "description": "${role_create-client}", 251 | "composite": false, 252 | "clientRole": true, 253 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 254 | "attributes": {} 255 | }, 256 | { 257 | "id": "442e8e74-4daa-440b-9ce6-ac75bbb4504b", 258 | "name": "manage-identity-providers", 259 | "description": "${role_manage-identity-providers}", 260 | "composite": false, 261 | "clientRole": true, 262 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 263 | "attributes": {} 264 | }, 265 | { 266 | "id": "fab7d197-ff54-4398-856c-2b2b3a9b3e41", 267 | "name": "view-events", 268 | "description": "${role_view-events}", 269 | "composite": false, 270 | "clientRole": true, 271 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 272 | "attributes": {} 273 | }, 274 | { 275 | "id": "55c66f43-5d7e-4b25-8a09-d79f0e2e3b3b", 276 | "name": "query-groups", 277 | "description": "${role_query-groups}", 278 | "composite": false, 279 | "clientRole": true, 280 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 281 | "attributes": {} 282 | }, 283 | { 284 | "id": "2b83c20a-9e42-4933-a0cb-8e3a6e79a86c", 285 | "name": "query-users", 286 | "description": "${role_query-users}", 287 | "composite": false, 288 | "clientRole": true, 289 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 290 | "attributes": {} 291 | }, 292 | { 293 | "id": "61cd943d-4da4-4999-b45b-861c37d5e963", 294 | "name": "view-authorization", 295 | "description": "${role_view-authorization}", 296 | "composite": false, 297 | "clientRole": true, 298 | "containerId": "34541706-6e55-4ab8-abec-ae1579d836a6", 299 | "attributes": {} 300 | } 301 | ], 302 | "security-admin-console": [], 303 | "admin-cli": [], 304 | "account-console": [], 305 | "broker": [ 306 | { 307 | "id": "cabf1bd5-938a-4424-bc98-c45ead806802", 308 | "name": "read-token", 309 | "description": "${role_read-token}", 310 | "composite": false, 311 | "clientRole": true, 312 | "containerId": "66cfd745-323a-4795-b906-58bea6de8f92", 313 | "attributes": {} 314 | } 315 | ], 316 | "account": [ 317 | { 318 | "id": "04d45935-e49a-45f8-a5b5-a9c9a4f5a2af", 319 | "name": "view-profile", 320 | "description": "${role_view-profile}", 321 | "composite": false, 322 | "clientRole": true, 323 | "containerId": "192597a2-4eab-455c-ae13-b43ffe897524", 324 | "attributes": {} 325 | }, 326 | { 327 | "id": "07c22ccd-34f8-48be-830a-96900c44f8cc", 328 | "name": "delete-account", 329 | "description": "${role_delete-account}", 330 | "composite": false, 331 | "clientRole": true, 332 | "containerId": "192597a2-4eab-455c-ae13-b43ffe897524", 333 | "attributes": {} 334 | }, 335 | { 336 | "id": "1fdac52b-7366-4bac-97d8-e94af667ac72", 337 | "name": "view-applications", 338 | "description": "${role_view-applications}", 339 | "composite": false, 340 | "clientRole": true, 341 | "containerId": "192597a2-4eab-455c-ae13-b43ffe897524", 342 | "attributes": {} 343 | }, 344 | { 345 | "id": "05b15e5f-e26d-4b7b-8514-bd179c0caf51", 346 | "name": "manage-account", 347 | "description": "${role_manage-account}", 348 | "composite": true, 349 | "composites": { 350 | "client": { 351 | "account": [ 352 | "manage-account-links" 353 | ] 354 | } 355 | }, 356 | "clientRole": true, 357 | "containerId": "192597a2-4eab-455c-ae13-b43ffe897524", 358 | "attributes": {} 359 | }, 360 | { 361 | "id": "10cc73b8-e676-4813-a7ec-093e9c7ad688", 362 | "name": "manage-consent", 363 | "description": "${role_manage-consent}", 364 | "composite": true, 365 | "composites": { 366 | "client": { 367 | "account": [ 368 | "view-consent" 369 | ] 370 | } 371 | }, 372 | "clientRole": true, 373 | "containerId": "192597a2-4eab-455c-ae13-b43ffe897524", 374 | "attributes": {} 375 | }, 376 | { 377 | "id": "2590165f-41b4-4523-9230-2107defb5d7c", 378 | "name": "view-consent", 379 | "description": "${role_view-consent}", 380 | "composite": false, 381 | "clientRole": true, 382 | "containerId": "192597a2-4eab-455c-ae13-b43ffe897524", 383 | "attributes": {} 384 | }, 385 | { 386 | "id": "5d7215a7-b9a2-42bf-82fc-a1f2bcca4155", 387 | "name": "manage-account-links", 388 | "description": "${role_manage-account-links}", 389 | "composite": false, 390 | "clientRole": true, 391 | "containerId": "192597a2-4eab-455c-ae13-b43ffe897524", 392 | "attributes": {} 393 | } 394 | ] 395 | } 396 | }, 397 | "groups": [], 398 | "defaultRole": { 399 | "id": "49764c24-8cd5-4ac2-b131-5cc5fbc50ace", 400 | "name": "default-roles-nestjs-keycloak", 401 | "description": "${role_default-roles}", 402 | "composite": true, 403 | "clientRole": false, 404 | "containerId": "nestjs-keycloak" 405 | }, 406 | "requiredCredentials": [ 407 | "password" 408 | ], 409 | "otpPolicyType": "totp", 410 | "otpPolicyAlgorithm": "HmacSHA1", 411 | "otpPolicyInitialCounter": 0, 412 | "otpPolicyDigits": 6, 413 | "otpPolicyLookAheadWindow": 1, 414 | "otpPolicyPeriod": 30, 415 | "otpSupportedApplications": [ 416 | "FreeOTP", 417 | "Google Authenticator" 418 | ], 419 | "webAuthnPolicyRpEntityName": "keycloak", 420 | "webAuthnPolicySignatureAlgorithms": [ 421 | "ES256" 422 | ], 423 | "webAuthnPolicyRpId": "", 424 | "webAuthnPolicyAttestationConveyancePreference": "not specified", 425 | "webAuthnPolicyAuthenticatorAttachment": "not specified", 426 | "webAuthnPolicyRequireResidentKey": "not specified", 427 | "webAuthnPolicyUserVerificationRequirement": "not specified", 428 | "webAuthnPolicyCreateTimeout": 0, 429 | "webAuthnPolicyAvoidSameAuthenticatorRegister": false, 430 | "webAuthnPolicyAcceptableAaguids": [], 431 | "webAuthnPolicyPasswordlessRpEntityName": "keycloak", 432 | "webAuthnPolicyPasswordlessSignatureAlgorithms": [ 433 | "ES256" 434 | ], 435 | "webAuthnPolicyPasswordlessRpId": "", 436 | "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", 437 | "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", 438 | "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", 439 | "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", 440 | "webAuthnPolicyPasswordlessCreateTimeout": 0, 441 | "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, 442 | "webAuthnPolicyPasswordlessAcceptableAaguids": [], 443 | "scopeMappings": [ 444 | { 445 | "clientScope": "offline_access", 446 | "roles": [ 447 | "offline_access" 448 | ] 449 | } 450 | ], 451 | "clientScopeMappings": { 452 | "account": [ 453 | { 454 | "client": "account-console", 455 | "roles": [ 456 | "manage-account" 457 | ] 458 | } 459 | ] 460 | }, 461 | "clients": [ 462 | { 463 | "id": "192597a2-4eab-455c-ae13-b43ffe897524", 464 | "clientId": "account", 465 | "name": "${client_account}", 466 | "rootUrl": "${authBaseUrl}", 467 | "baseUrl": "/realms/nestjs-keycloak/account/", 468 | "surrogateAuthRequired": false, 469 | "enabled": true, 470 | "alwaysDisplayInConsole": false, 471 | "clientAuthenticatorType": "client-secret", 472 | "redirectUris": [ 473 | "/realms/nestjs-keycloak/account/*" 474 | ], 475 | "webOrigins": [], 476 | "notBefore": 0, 477 | "bearerOnly": false, 478 | "consentRequired": false, 479 | "standardFlowEnabled": true, 480 | "implicitFlowEnabled": false, 481 | "directAccessGrantsEnabled": false, 482 | "serviceAccountsEnabled": false, 483 | "publicClient": true, 484 | "frontchannelLogout": false, 485 | "protocol": "openid-connect", 486 | "attributes": {}, 487 | "authenticationFlowBindingOverrides": {}, 488 | "fullScopeAllowed": false, 489 | "nodeReRegistrationTimeout": 0, 490 | "defaultClientScopes": [ 491 | "web-origins", 492 | "profile", 493 | "roles", 494 | "email" 495 | ], 496 | "optionalClientScopes": [ 497 | "address", 498 | "phone", 499 | "offline_access", 500 | "microprofile-jwt" 501 | ] 502 | }, 503 | { 504 | "id": "41cf341c-ac89-4f3c-aa1a-80259ffa84b2", 505 | "clientId": "account-console", 506 | "name": "${client_account-console}", 507 | "rootUrl": "${authBaseUrl}", 508 | "baseUrl": "/realms/nestjs-keycloak/account/", 509 | "surrogateAuthRequired": false, 510 | "enabled": true, 511 | "alwaysDisplayInConsole": false, 512 | "clientAuthenticatorType": "client-secret", 513 | "redirectUris": [ 514 | "/realms/nestjs-keycloak/account/*" 515 | ], 516 | "webOrigins": [], 517 | "notBefore": 0, 518 | "bearerOnly": false, 519 | "consentRequired": false, 520 | "standardFlowEnabled": true, 521 | "implicitFlowEnabled": false, 522 | "directAccessGrantsEnabled": false, 523 | "serviceAccountsEnabled": false, 524 | "publicClient": true, 525 | "frontchannelLogout": false, 526 | "protocol": "openid-connect", 527 | "attributes": { 528 | "pkce.code.challenge.method": "S256" 529 | }, 530 | "authenticationFlowBindingOverrides": {}, 531 | "fullScopeAllowed": false, 532 | "nodeReRegistrationTimeout": 0, 533 | "protocolMappers": [ 534 | { 535 | "id": "8470b0cd-27f4-4db1-8d95-d107eab8a111", 536 | "name": "audience resolve", 537 | "protocol": "openid-connect", 538 | "protocolMapper": "oidc-audience-resolve-mapper", 539 | "consentRequired": false, 540 | "config": {} 541 | } 542 | ], 543 | "defaultClientScopes": [ 544 | "web-origins", 545 | "profile", 546 | "roles", 547 | "email" 548 | ], 549 | "optionalClientScopes": [ 550 | "address", 551 | "phone", 552 | "offline_access", 553 | "microprofile-jwt" 554 | ] 555 | }, 556 | { 557 | "id": "de69e746-564d-4a34-970b-d8fdc6b1b49f", 558 | "clientId": "admin-cli", 559 | "name": "${client_admin-cli}", 560 | "surrogateAuthRequired": false, 561 | "enabled": true, 562 | "alwaysDisplayInConsole": false, 563 | "clientAuthenticatorType": "client-secret", 564 | "redirectUris": [], 565 | "webOrigins": [], 566 | "notBefore": 0, 567 | "bearerOnly": false, 568 | "consentRequired": false, 569 | "standardFlowEnabled": false, 570 | "implicitFlowEnabled": false, 571 | "directAccessGrantsEnabled": true, 572 | "serviceAccountsEnabled": false, 573 | "publicClient": true, 574 | "frontchannelLogout": false, 575 | "protocol": "openid-connect", 576 | "attributes": {}, 577 | "authenticationFlowBindingOverrides": {}, 578 | "fullScopeAllowed": false, 579 | "nodeReRegistrationTimeout": 0, 580 | "defaultClientScopes": [ 581 | "web-origins", 582 | "profile", 583 | "roles", 584 | "email" 585 | ], 586 | "optionalClientScopes": [ 587 | "address", 588 | "phone", 589 | "offline_access", 590 | "microprofile-jwt" 591 | ] 592 | }, 593 | { 594 | "id": "66cfd745-323a-4795-b906-58bea6de8f92", 595 | "clientId": "broker", 596 | "name": "${client_broker}", 597 | "surrogateAuthRequired": false, 598 | "enabled": true, 599 | "alwaysDisplayInConsole": false, 600 | "clientAuthenticatorType": "client-secret", 601 | "redirectUris": [], 602 | "webOrigins": [], 603 | "notBefore": 0, 604 | "bearerOnly": true, 605 | "consentRequired": false, 606 | "standardFlowEnabled": true, 607 | "implicitFlowEnabled": false, 608 | "directAccessGrantsEnabled": false, 609 | "serviceAccountsEnabled": false, 610 | "publicClient": false, 611 | "frontchannelLogout": false, 612 | "protocol": "openid-connect", 613 | "attributes": {}, 614 | "authenticationFlowBindingOverrides": {}, 615 | "fullScopeAllowed": false, 616 | "nodeReRegistrationTimeout": 0, 617 | "defaultClientScopes": [ 618 | "web-origins", 619 | "profile", 620 | "roles", 621 | "email" 622 | ], 623 | "optionalClientScopes": [ 624 | "address", 625 | "phone", 626 | "offline_access", 627 | "microprofile-jwt" 628 | ] 629 | }, 630 | { 631 | "id": "5f55a045-e1df-45f6-acd8-88bfc0918ec0", 632 | "clientId": "nestjs-keycloak", 633 | "surrogateAuthRequired": false, 634 | "enabled": true, 635 | "alwaysDisplayInConsole": false, 636 | "clientAuthenticatorType": "client-secret", 637 | "secret": "**********", 638 | "redirectUris": [ 639 | "*" 640 | ], 641 | "webOrigins": [], 642 | "notBefore": 0, 643 | "bearerOnly": false, 644 | "consentRequired": false, 645 | "standardFlowEnabled": true, 646 | "implicitFlowEnabled": false, 647 | "directAccessGrantsEnabled": true, 648 | "serviceAccountsEnabled": false, 649 | "publicClient": false, 650 | "frontchannelLogout": false, 651 | "protocol": "openid-connect", 652 | "attributes": { 653 | "id.token.as.detached.signature": "false", 654 | "saml.assertion.signature": "false", 655 | "saml.force.post.binding": "false", 656 | "saml.multivalued.roles": "false", 657 | "saml.encrypt": "false", 658 | "oauth2.device.authorization.grant.enabled": "false", 659 | "backchannel.logout.revoke.offline.tokens": "false", 660 | "saml.server.signature": "false", 661 | "saml.server.signature.keyinfo.ext": "false", 662 | "use.refresh.tokens": "true", 663 | "exclude.session.state.from.auth.response": "false", 664 | "oidc.ciba.grant.enabled": "false", 665 | "saml.artifact.binding": "false", 666 | "backchannel.logout.session.required": "true", 667 | "client_credentials.use_refresh_token": "false", 668 | "saml_force_name_id_format": "false", 669 | "require.pushed.authorization.requests": "false", 670 | "saml.client.signature": "false", 671 | "tls.client.certificate.bound.access.tokens": "false", 672 | "saml.authnstatement": "false", 673 | "display.on.consent.screen": "false", 674 | "saml.onetimeuse.condition": "false" 675 | }, 676 | "authenticationFlowBindingOverrides": {}, 677 | "fullScopeAllowed": true, 678 | "nodeReRegistrationTimeout": -1, 679 | "defaultClientScopes": [ 680 | "web-origins", 681 | "profile", 682 | "roles", 683 | "email" 684 | ], 685 | "optionalClientScopes": [ 686 | "address", 687 | "phone", 688 | "offline_access", 689 | "microprofile-jwt" 690 | ] 691 | }, 692 | { 693 | "id": "34541706-6e55-4ab8-abec-ae1579d836a6", 694 | "clientId": "realm-management", 695 | "name": "${client_realm-management}", 696 | "surrogateAuthRequired": false, 697 | "enabled": true, 698 | "alwaysDisplayInConsole": false, 699 | "clientAuthenticatorType": "client-secret", 700 | "redirectUris": [], 701 | "webOrigins": [], 702 | "notBefore": 0, 703 | "bearerOnly": true, 704 | "consentRequired": false, 705 | "standardFlowEnabled": true, 706 | "implicitFlowEnabled": false, 707 | "directAccessGrantsEnabled": false, 708 | "serviceAccountsEnabled": false, 709 | "publicClient": false, 710 | "frontchannelLogout": false, 711 | "protocol": "openid-connect", 712 | "attributes": {}, 713 | "authenticationFlowBindingOverrides": {}, 714 | "fullScopeAllowed": false, 715 | "nodeReRegistrationTimeout": 0, 716 | "defaultClientScopes": [ 717 | "web-origins", 718 | "profile", 719 | "roles", 720 | "email" 721 | ], 722 | "optionalClientScopes": [ 723 | "address", 724 | "phone", 725 | "offline_access", 726 | "microprofile-jwt" 727 | ] 728 | }, 729 | { 730 | "id": "8d341dfc-1d0d-4089-9b21-ed452056e9cd", 731 | "clientId": "security-admin-console", 732 | "name": "${client_security-admin-console}", 733 | "rootUrl": "${authAdminUrl}", 734 | "baseUrl": "/admin/nestjs-keycloak/console/", 735 | "surrogateAuthRequired": false, 736 | "enabled": true, 737 | "alwaysDisplayInConsole": false, 738 | "clientAuthenticatorType": "client-secret", 739 | "redirectUris": [ 740 | "/admin/nestjs-keycloak/console/*" 741 | ], 742 | "webOrigins": [ 743 | "+" 744 | ], 745 | "notBefore": 0, 746 | "bearerOnly": false, 747 | "consentRequired": false, 748 | "standardFlowEnabled": true, 749 | "implicitFlowEnabled": false, 750 | "directAccessGrantsEnabled": false, 751 | "serviceAccountsEnabled": false, 752 | "publicClient": true, 753 | "frontchannelLogout": false, 754 | "protocol": "openid-connect", 755 | "attributes": { 756 | "pkce.code.challenge.method": "S256" 757 | }, 758 | "authenticationFlowBindingOverrides": {}, 759 | "fullScopeAllowed": false, 760 | "nodeReRegistrationTimeout": 0, 761 | "protocolMappers": [ 762 | { 763 | "id": "08d10c90-0a80-4018-96ed-cdece4317b3b", 764 | "name": "locale", 765 | "protocol": "openid-connect", 766 | "protocolMapper": "oidc-usermodel-attribute-mapper", 767 | "consentRequired": false, 768 | "config": { 769 | "userinfo.token.claim": "true", 770 | "user.attribute": "locale", 771 | "id.token.claim": "true", 772 | "access.token.claim": "true", 773 | "claim.name": "locale", 774 | "jsonType.label": "String" 775 | } 776 | } 777 | ], 778 | "defaultClientScopes": [ 779 | "web-origins", 780 | "profile", 781 | "roles", 782 | "email" 783 | ], 784 | "optionalClientScopes": [ 785 | "address", 786 | "phone", 787 | "offline_access", 788 | "microprofile-jwt" 789 | ] 790 | } 791 | ], 792 | "clientScopes": [ 793 | { 794 | "id": "507ecf2e-c767-4a24-a1d9-82742cd30443", 795 | "name": "email", 796 | "description": "OpenID Connect built-in scope: email", 797 | "protocol": "openid-connect", 798 | "attributes": { 799 | "include.in.token.scope": "true", 800 | "display.on.consent.screen": "true", 801 | "consent.screen.text": "${emailScopeConsentText}" 802 | }, 803 | "protocolMappers": [ 804 | { 805 | "id": "60df8515-98fc-4c30-8c6d-430f26fa6265", 806 | "name": "email verified", 807 | "protocol": "openid-connect", 808 | "protocolMapper": "oidc-usermodel-property-mapper", 809 | "consentRequired": false, 810 | "config": { 811 | "userinfo.token.claim": "true", 812 | "user.attribute": "emailVerified", 813 | "id.token.claim": "true", 814 | "access.token.claim": "true", 815 | "claim.name": "email_verified", 816 | "jsonType.label": "boolean" 817 | } 818 | }, 819 | { 820 | "id": "1ea8664d-db91-4f8a-b5b6-360b41bbac81", 821 | "name": "email", 822 | "protocol": "openid-connect", 823 | "protocolMapper": "oidc-usermodel-property-mapper", 824 | "consentRequired": false, 825 | "config": { 826 | "userinfo.token.claim": "true", 827 | "user.attribute": "email", 828 | "id.token.claim": "true", 829 | "access.token.claim": "true", 830 | "claim.name": "email", 831 | "jsonType.label": "String" 832 | } 833 | } 834 | ] 835 | }, 836 | { 837 | "id": "bf8dcdbd-abe1-4d05-9e6d-c286fb10a52a", 838 | "name": "profile", 839 | "description": "OpenID Connect built-in scope: profile", 840 | "protocol": "openid-connect", 841 | "attributes": { 842 | "include.in.token.scope": "true", 843 | "display.on.consent.screen": "true", 844 | "consent.screen.text": "${profileScopeConsentText}" 845 | }, 846 | "protocolMappers": [ 847 | { 848 | "id": "f213d619-fe9a-4ec5-935f-cf533912137b", 849 | "name": "gender", 850 | "protocol": "openid-connect", 851 | "protocolMapper": "oidc-usermodel-attribute-mapper", 852 | "consentRequired": false, 853 | "config": { 854 | "userinfo.token.claim": "true", 855 | "user.attribute": "gender", 856 | "id.token.claim": "true", 857 | "access.token.claim": "true", 858 | "claim.name": "gender", 859 | "jsonType.label": "String" 860 | } 861 | }, 862 | { 863 | "id": "a4730fac-a31a-448e-9f73-a6e046dfbb0a", 864 | "name": "username", 865 | "protocol": "openid-connect", 866 | "protocolMapper": "oidc-usermodel-property-mapper", 867 | "consentRequired": false, 868 | "config": { 869 | "userinfo.token.claim": "true", 870 | "user.attribute": "username", 871 | "id.token.claim": "true", 872 | "access.token.claim": "true", 873 | "claim.name": "preferred_username", 874 | "jsonType.label": "String" 875 | } 876 | }, 877 | { 878 | "id": "7ff35968-b7a6-4604-ad33-1c4f4e055b21", 879 | "name": "birthdate", 880 | "protocol": "openid-connect", 881 | "protocolMapper": "oidc-usermodel-attribute-mapper", 882 | "consentRequired": false, 883 | "config": { 884 | "userinfo.token.claim": "true", 885 | "user.attribute": "birthdate", 886 | "id.token.claim": "true", 887 | "access.token.claim": "true", 888 | "claim.name": "birthdate", 889 | "jsonType.label": "String" 890 | } 891 | }, 892 | { 893 | "id": "691df665-64e4-4e5a-a686-aa3c941359f2", 894 | "name": "zoneinfo", 895 | "protocol": "openid-connect", 896 | "protocolMapper": "oidc-usermodel-attribute-mapper", 897 | "consentRequired": false, 898 | "config": { 899 | "userinfo.token.claim": "true", 900 | "user.attribute": "zoneinfo", 901 | "id.token.claim": "true", 902 | "access.token.claim": "true", 903 | "claim.name": "zoneinfo", 904 | "jsonType.label": "String" 905 | } 906 | }, 907 | { 908 | "id": "1bcdd72c-635e-4cd7-93a2-a00378142bbf", 909 | "name": "nickname", 910 | "protocol": "openid-connect", 911 | "protocolMapper": "oidc-usermodel-attribute-mapper", 912 | "consentRequired": false, 913 | "config": { 914 | "userinfo.token.claim": "true", 915 | "user.attribute": "nickname", 916 | "id.token.claim": "true", 917 | "access.token.claim": "true", 918 | "claim.name": "nickname", 919 | "jsonType.label": "String" 920 | } 921 | }, 922 | { 923 | "id": "465d3e75-2d27-48ce-bc68-4072f1478e78", 924 | "name": "given name", 925 | "protocol": "openid-connect", 926 | "protocolMapper": "oidc-usermodel-property-mapper", 927 | "consentRequired": false, 928 | "config": { 929 | "userinfo.token.claim": "true", 930 | "user.attribute": "firstName", 931 | "id.token.claim": "true", 932 | "access.token.claim": "true", 933 | "claim.name": "given_name", 934 | "jsonType.label": "String" 935 | } 936 | }, 937 | { 938 | "id": "56ee0dc3-bc75-47fd-9fce-e875fb5b3ef0", 939 | "name": "profile", 940 | "protocol": "openid-connect", 941 | "protocolMapper": "oidc-usermodel-attribute-mapper", 942 | "consentRequired": false, 943 | "config": { 944 | "userinfo.token.claim": "true", 945 | "user.attribute": "profile", 946 | "id.token.claim": "true", 947 | "access.token.claim": "true", 948 | "claim.name": "profile", 949 | "jsonType.label": "String" 950 | } 951 | }, 952 | { 953 | "id": "fd2761fa-5b7f-41e5-9225-df0d2b7b614a", 954 | "name": "picture", 955 | "protocol": "openid-connect", 956 | "protocolMapper": "oidc-usermodel-attribute-mapper", 957 | "consentRequired": false, 958 | "config": { 959 | "userinfo.token.claim": "true", 960 | "user.attribute": "picture", 961 | "id.token.claim": "true", 962 | "access.token.claim": "true", 963 | "claim.name": "picture", 964 | "jsonType.label": "String" 965 | } 966 | }, 967 | { 968 | "id": "1e3085cf-63e6-4afa-a472-4175c745c579", 969 | "name": "full name", 970 | "protocol": "openid-connect", 971 | "protocolMapper": "oidc-full-name-mapper", 972 | "consentRequired": false, 973 | "config": { 974 | "id.token.claim": "true", 975 | "access.token.claim": "true", 976 | "userinfo.token.claim": "true" 977 | } 978 | }, 979 | { 980 | "id": "51306ed8-b10a-4aef-889c-481b5dffee70", 981 | "name": "locale", 982 | "protocol": "openid-connect", 983 | "protocolMapper": "oidc-usermodel-attribute-mapper", 984 | "consentRequired": false, 985 | "config": { 986 | "userinfo.token.claim": "true", 987 | "user.attribute": "locale", 988 | "id.token.claim": "true", 989 | "access.token.claim": "true", 990 | "claim.name": "locale", 991 | "jsonType.label": "String" 992 | } 993 | }, 994 | { 995 | "id": "2000f387-d414-4452-9ae7-462688cbf2c0", 996 | "name": "family name", 997 | "protocol": "openid-connect", 998 | "protocolMapper": "oidc-usermodel-property-mapper", 999 | "consentRequired": false, 1000 | "config": { 1001 | "userinfo.token.claim": "true", 1002 | "user.attribute": "lastName", 1003 | "id.token.claim": "true", 1004 | "access.token.claim": "true", 1005 | "claim.name": "family_name", 1006 | "jsonType.label": "String" 1007 | } 1008 | }, 1009 | { 1010 | "id": "ac456933-04d3-41ad-9afc-46add8fb4c0a", 1011 | "name": "updated at", 1012 | "protocol": "openid-connect", 1013 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1014 | "consentRequired": false, 1015 | "config": { 1016 | "userinfo.token.claim": "true", 1017 | "user.attribute": "updatedAt", 1018 | "id.token.claim": "true", 1019 | "access.token.claim": "true", 1020 | "claim.name": "updated_at", 1021 | "jsonType.label": "String" 1022 | } 1023 | }, 1024 | { 1025 | "id": "f9e63482-c134-4b90-b5d5-e1f1c80d94d1", 1026 | "name": "website", 1027 | "protocol": "openid-connect", 1028 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1029 | "consentRequired": false, 1030 | "config": { 1031 | "userinfo.token.claim": "true", 1032 | "user.attribute": "website", 1033 | "id.token.claim": "true", 1034 | "access.token.claim": "true", 1035 | "claim.name": "website", 1036 | "jsonType.label": "String" 1037 | } 1038 | }, 1039 | { 1040 | "id": "3742a37e-7111-41a9-b1ea-d60c9b487a8c", 1041 | "name": "middle name", 1042 | "protocol": "openid-connect", 1043 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1044 | "consentRequired": false, 1045 | "config": { 1046 | "userinfo.token.claim": "true", 1047 | "user.attribute": "middleName", 1048 | "id.token.claim": "true", 1049 | "access.token.claim": "true", 1050 | "claim.name": "middle_name", 1051 | "jsonType.label": "String" 1052 | } 1053 | } 1054 | ] 1055 | }, 1056 | { 1057 | "id": "bc78f43b-9aca-427d-b936-9ea0cd5e0d1a", 1058 | "name": "role_list", 1059 | "description": "SAML role list", 1060 | "protocol": "saml", 1061 | "attributes": { 1062 | "consent.screen.text": "${samlRoleListScopeConsentText}", 1063 | "display.on.consent.screen": "true" 1064 | }, 1065 | "protocolMappers": [ 1066 | { 1067 | "id": "0ed6e3f6-ceec-46b0-8d66-c869dcd682f7", 1068 | "name": "role list", 1069 | "protocol": "saml", 1070 | "protocolMapper": "saml-role-list-mapper", 1071 | "consentRequired": false, 1072 | "config": { 1073 | "single": "false", 1074 | "attribute.nameformat": "Basic", 1075 | "attribute.name": "Role" 1076 | } 1077 | } 1078 | ] 1079 | }, 1080 | { 1081 | "id": "fc3f7d06-7bb7-4a1a-bf26-0b90056670ff", 1082 | "name": "offline_access", 1083 | "description": "OpenID Connect built-in scope: offline_access", 1084 | "protocol": "openid-connect", 1085 | "attributes": { 1086 | "consent.screen.text": "${offlineAccessScopeConsentText}", 1087 | "display.on.consent.screen": "true" 1088 | } 1089 | }, 1090 | { 1091 | "id": "e20f4c8e-f9a2-44a2-90b4-102c886a6683", 1092 | "name": "address", 1093 | "description": "OpenID Connect built-in scope: address", 1094 | "protocol": "openid-connect", 1095 | "attributes": { 1096 | "include.in.token.scope": "true", 1097 | "display.on.consent.screen": "true", 1098 | "consent.screen.text": "${addressScopeConsentText}" 1099 | }, 1100 | "protocolMappers": [ 1101 | { 1102 | "id": "bc009978-b613-4e90-93b0-cbc1dc53ea94", 1103 | "name": "address", 1104 | "protocol": "openid-connect", 1105 | "protocolMapper": "oidc-address-mapper", 1106 | "consentRequired": false, 1107 | "config": { 1108 | "user.attribute.formatted": "formatted", 1109 | "user.attribute.country": "country", 1110 | "user.attribute.postal_code": "postal_code", 1111 | "userinfo.token.claim": "true", 1112 | "user.attribute.street": "street", 1113 | "id.token.claim": "true", 1114 | "user.attribute.region": "region", 1115 | "access.token.claim": "true", 1116 | "user.attribute.locality": "locality" 1117 | } 1118 | } 1119 | ] 1120 | }, 1121 | { 1122 | "id": "f7486dfe-0f81-4386-a4f9-51b8982c5fc6", 1123 | "name": "microprofile-jwt", 1124 | "description": "Microprofile - JWT built-in scope", 1125 | "protocol": "openid-connect", 1126 | "attributes": { 1127 | "include.in.token.scope": "true", 1128 | "display.on.consent.screen": "false" 1129 | }, 1130 | "protocolMappers": [ 1131 | { 1132 | "id": "bfc7b96b-212e-4271-913f-fac44efcd365", 1133 | "name": "upn", 1134 | "protocol": "openid-connect", 1135 | "protocolMapper": "oidc-usermodel-property-mapper", 1136 | "consentRequired": false, 1137 | "config": { 1138 | "userinfo.token.claim": "true", 1139 | "user.attribute": "username", 1140 | "id.token.claim": "true", 1141 | "access.token.claim": "true", 1142 | "claim.name": "upn", 1143 | "jsonType.label": "String" 1144 | } 1145 | }, 1146 | { 1147 | "id": "1ad934fd-cd38-47e8-8cf7-d4568c8d54ff", 1148 | "name": "groups", 1149 | "protocol": "openid-connect", 1150 | "protocolMapper": "oidc-usermodel-realm-role-mapper", 1151 | "consentRequired": false, 1152 | "config": { 1153 | "multivalued": "true", 1154 | "user.attribute": "foo", 1155 | "id.token.claim": "true", 1156 | "access.token.claim": "true", 1157 | "claim.name": "groups", 1158 | "jsonType.label": "String" 1159 | } 1160 | } 1161 | ] 1162 | }, 1163 | { 1164 | "id": "7f710c40-2a44-420b-879b-4fd0bea2041a", 1165 | "name": "phone", 1166 | "description": "OpenID Connect built-in scope: phone", 1167 | "protocol": "openid-connect", 1168 | "attributes": { 1169 | "include.in.token.scope": "true", 1170 | "display.on.consent.screen": "true", 1171 | "consent.screen.text": "${phoneScopeConsentText}" 1172 | }, 1173 | "protocolMappers": [ 1174 | { 1175 | "id": "36171c77-0bd6-45f0-9108-30c46001bf7a", 1176 | "name": "phone number", 1177 | "protocol": "openid-connect", 1178 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1179 | "consentRequired": false, 1180 | "config": { 1181 | "userinfo.token.claim": "true", 1182 | "user.attribute": "phoneNumber", 1183 | "id.token.claim": "true", 1184 | "access.token.claim": "true", 1185 | "claim.name": "phone_number", 1186 | "jsonType.label": "String" 1187 | } 1188 | }, 1189 | { 1190 | "id": "d87f448c-11bf-44c5-87cc-93b4c2d1cd66", 1191 | "name": "phone number verified", 1192 | "protocol": "openid-connect", 1193 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1194 | "consentRequired": false, 1195 | "config": { 1196 | "userinfo.token.claim": "true", 1197 | "user.attribute": "phoneNumberVerified", 1198 | "id.token.claim": "true", 1199 | "access.token.claim": "true", 1200 | "claim.name": "phone_number_verified", 1201 | "jsonType.label": "boolean" 1202 | } 1203 | } 1204 | ] 1205 | }, 1206 | { 1207 | "id": "db9d6a28-bdb5-4986-9b7a-6bf0b6d4bf00", 1208 | "name": "web-origins", 1209 | "description": "OpenID Connect scope for add allowed web origins to the access token", 1210 | "protocol": "openid-connect", 1211 | "attributes": { 1212 | "include.in.token.scope": "false", 1213 | "display.on.consent.screen": "false", 1214 | "consent.screen.text": "" 1215 | }, 1216 | "protocolMappers": [ 1217 | { 1218 | "id": "4e3fc2a6-c9ba-427b-8636-1aa8f30fbdc4", 1219 | "name": "allowed web origins", 1220 | "protocol": "openid-connect", 1221 | "protocolMapper": "oidc-allowed-origins-mapper", 1222 | "consentRequired": false, 1223 | "config": {} 1224 | } 1225 | ] 1226 | }, 1227 | { 1228 | "id": "a45b6b04-1301-4013-bafa-28001127375f", 1229 | "name": "roles", 1230 | "description": "OpenID Connect scope for add user roles to the access token", 1231 | "protocol": "openid-connect", 1232 | "attributes": { 1233 | "include.in.token.scope": "false", 1234 | "display.on.consent.screen": "true", 1235 | "consent.screen.text": "${rolesScopeConsentText}" 1236 | }, 1237 | "protocolMappers": [ 1238 | { 1239 | "id": "e2b544aa-0ad4-43d2-86c6-3a40073e2bae", 1240 | "name": "client roles", 1241 | "protocol": "openid-connect", 1242 | "protocolMapper": "oidc-usermodel-client-role-mapper", 1243 | "consentRequired": false, 1244 | "config": { 1245 | "user.attribute": "foo", 1246 | "access.token.claim": "true", 1247 | "claim.name": "resource_access.${client_id}.roles", 1248 | "jsonType.label": "String", 1249 | "multivalued": "true" 1250 | } 1251 | }, 1252 | { 1253 | "id": "f3f58e1b-8eca-46ca-bb30-9e0977a34dd9", 1254 | "name": "realm roles", 1255 | "protocol": "openid-connect", 1256 | "protocolMapper": "oidc-usermodel-realm-role-mapper", 1257 | "consentRequired": false, 1258 | "config": { 1259 | "user.attribute": "foo", 1260 | "access.token.claim": "true", 1261 | "claim.name": "realm_access.roles", 1262 | "jsonType.label": "String", 1263 | "multivalued": "true" 1264 | } 1265 | }, 1266 | { 1267 | "id": "97ae5a6c-7af0-4863-acdf-0d215ba7f682", 1268 | "name": "audience resolve", 1269 | "protocol": "openid-connect", 1270 | "protocolMapper": "oidc-audience-resolve-mapper", 1271 | "consentRequired": false, 1272 | "config": {} 1273 | } 1274 | ] 1275 | } 1276 | ], 1277 | "defaultDefaultClientScopes": [ 1278 | "email", 1279 | "roles", 1280 | "role_list", 1281 | "profile", 1282 | "web-origins" 1283 | ], 1284 | "defaultOptionalClientScopes": [ 1285 | "phone", 1286 | "address", 1287 | "microprofile-jwt", 1288 | "offline_access" 1289 | ], 1290 | "browserSecurityHeaders": { 1291 | "contentSecurityPolicyReportOnly": "", 1292 | "xContentTypeOptions": "nosniff", 1293 | "xRobotsTag": "none", 1294 | "xFrameOptions": "SAMEORIGIN", 1295 | "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", 1296 | "xXSSProtection": "1; mode=block", 1297 | "strictTransportSecurity": "max-age=31536000; includeSubDomains" 1298 | }, 1299 | "smtpServer": {}, 1300 | "eventsEnabled": false, 1301 | "eventsListeners": [ 1302 | "jboss-logging" 1303 | ], 1304 | "enabledEventTypes": [], 1305 | "adminEventsEnabled": false, 1306 | "adminEventsDetailsEnabled": false, 1307 | "identityProviders": [], 1308 | "identityProviderMappers": [], 1309 | "components": { 1310 | "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ 1311 | { 1312 | "id": "0b487122-36bd-4ac0-9357-ecc74de16f9e", 1313 | "name": "Allowed Client Scopes", 1314 | "providerId": "allowed-client-templates", 1315 | "subType": "authenticated", 1316 | "subComponents": {}, 1317 | "config": { 1318 | "allow-default-scopes": [ 1319 | "true" 1320 | ] 1321 | } 1322 | }, 1323 | { 1324 | "id": "ead07ee8-66ef-4878-a809-c8b4dff284f1", 1325 | "name": "Allowed Protocol Mapper Types", 1326 | "providerId": "allowed-protocol-mappers", 1327 | "subType": "anonymous", 1328 | "subComponents": {}, 1329 | "config": { 1330 | "allowed-protocol-mapper-types": [ 1331 | "oidc-address-mapper", 1332 | "oidc-sha256-pairwise-sub-mapper", 1333 | "saml-role-list-mapper", 1334 | "oidc-usermodel-attribute-mapper", 1335 | "oidc-full-name-mapper", 1336 | "saml-user-property-mapper", 1337 | "saml-user-attribute-mapper", 1338 | "oidc-usermodel-property-mapper" 1339 | ] 1340 | } 1341 | }, 1342 | { 1343 | "id": "0107c962-1c9b-4cf7-853f-29f0d39ef5de", 1344 | "name": "Allowed Client Scopes", 1345 | "providerId": "allowed-client-templates", 1346 | "subType": "anonymous", 1347 | "subComponents": {}, 1348 | "config": { 1349 | "allow-default-scopes": [ 1350 | "true" 1351 | ] 1352 | } 1353 | }, 1354 | { 1355 | "id": "865a1af2-9365-43a9-95ff-1567c5b9b4aa", 1356 | "name": "Consent Required", 1357 | "providerId": "consent-required", 1358 | "subType": "anonymous", 1359 | "subComponents": {}, 1360 | "config": {} 1361 | }, 1362 | { 1363 | "id": "49b31a66-cff7-4155-8fed-076bf50aa037", 1364 | "name": "Trusted Hosts", 1365 | "providerId": "trusted-hosts", 1366 | "subType": "anonymous", 1367 | "subComponents": {}, 1368 | "config": { 1369 | "host-sending-registration-request-must-match": [ 1370 | "true" 1371 | ], 1372 | "client-uris-must-match": [ 1373 | "true" 1374 | ] 1375 | } 1376 | }, 1377 | { 1378 | "id": "2bd06748-6d30-4f32-86d5-128795a7297d", 1379 | "name": "Full Scope Disabled", 1380 | "providerId": "scope", 1381 | "subType": "anonymous", 1382 | "subComponents": {}, 1383 | "config": {} 1384 | }, 1385 | { 1386 | "id": "07fee7ac-68d2-4895-82b9-ccdc06c20e8c", 1387 | "name": "Max Clients Limit", 1388 | "providerId": "max-clients", 1389 | "subType": "anonymous", 1390 | "subComponents": {}, 1391 | "config": { 1392 | "max-clients": [ 1393 | "200" 1394 | ] 1395 | } 1396 | }, 1397 | { 1398 | "id": "3082e42c-e500-40f0-8f82-298a4f67b984", 1399 | "name": "Allowed Protocol Mapper Types", 1400 | "providerId": "allowed-protocol-mappers", 1401 | "subType": "authenticated", 1402 | "subComponents": {}, 1403 | "config": { 1404 | "allowed-protocol-mapper-types": [ 1405 | "oidc-usermodel-attribute-mapper", 1406 | "oidc-sha256-pairwise-sub-mapper", 1407 | "oidc-full-name-mapper", 1408 | "oidc-address-mapper", 1409 | "saml-role-list-mapper", 1410 | "saml-user-property-mapper", 1411 | "oidc-usermodel-property-mapper", 1412 | "saml-user-attribute-mapper" 1413 | ] 1414 | } 1415 | } 1416 | ], 1417 | "org.keycloak.keys.KeyProvider": [ 1418 | { 1419 | "id": "22a444dd-0bba-4ed9-ae92-acd17f1f72cf", 1420 | "name": "rsa-enc-generated", 1421 | "providerId": "rsa-generated", 1422 | "subComponents": {}, 1423 | "config": { 1424 | "keyUse": [ 1425 | "enc" 1426 | ], 1427 | "priority": [ 1428 | "100" 1429 | ] 1430 | } 1431 | }, 1432 | { 1433 | "id": "c7f1d563-08f9-41f1-89f5-8d7378eab3f5", 1434 | "name": "hmac-generated", 1435 | "providerId": "hmac-generated", 1436 | "subComponents": {}, 1437 | "config": { 1438 | "priority": [ 1439 | "100" 1440 | ], 1441 | "algorithm": [ 1442 | "HS256" 1443 | ] 1444 | } 1445 | }, 1446 | { 1447 | "id": "dc603cbd-d197-4d9c-944b-b24a80f02b99", 1448 | "name": "aes-generated", 1449 | "providerId": "aes-generated", 1450 | "subComponents": {}, 1451 | "config": { 1452 | "priority": [ 1453 | "100" 1454 | ] 1455 | } 1456 | }, 1457 | { 1458 | "id": "92e3f198-5a02-4855-bf4c-725044fc1adc", 1459 | "name": "rsa-generated", 1460 | "providerId": "rsa-generated", 1461 | "subComponents": {}, 1462 | "config": { 1463 | "keyUse": [ 1464 | "sig" 1465 | ], 1466 | "priority": [ 1467 | "100" 1468 | ] 1469 | } 1470 | } 1471 | ] 1472 | }, 1473 | "internationalizationEnabled": false, 1474 | "supportedLocales": [], 1475 | "authenticationFlows": [ 1476 | { 1477 | "id": "25344a58-7022-41a5-8a71-1af5f2dfed54", 1478 | "alias": "Account verification options", 1479 | "description": "Method with which to verity the existing account", 1480 | "providerId": "basic-flow", 1481 | "topLevel": false, 1482 | "builtIn": true, 1483 | "authenticationExecutions": [ 1484 | { 1485 | "authenticator": "idp-email-verification", 1486 | "authenticatorFlow": false, 1487 | "requirement": "ALTERNATIVE", 1488 | "priority": 10, 1489 | "userSetupAllowed": false, 1490 | "autheticatorFlow": false 1491 | }, 1492 | { 1493 | "authenticatorFlow": true, 1494 | "requirement": "ALTERNATIVE", 1495 | "priority": 20, 1496 | "flowAlias": "Verify Existing Account by Re-authentication", 1497 | "userSetupAllowed": false, 1498 | "autheticatorFlow": true 1499 | } 1500 | ] 1501 | }, 1502 | { 1503 | "id": "9523e2e3-72c8-4fd9-88a1-934d8ea0ef0d", 1504 | "alias": "Authentication Options", 1505 | "description": "Authentication options.", 1506 | "providerId": "basic-flow", 1507 | "topLevel": false, 1508 | "builtIn": true, 1509 | "authenticationExecutions": [ 1510 | { 1511 | "authenticator": "basic-auth", 1512 | "authenticatorFlow": false, 1513 | "requirement": "REQUIRED", 1514 | "priority": 10, 1515 | "userSetupAllowed": false, 1516 | "autheticatorFlow": false 1517 | }, 1518 | { 1519 | "authenticator": "basic-auth-otp", 1520 | "authenticatorFlow": false, 1521 | "requirement": "DISABLED", 1522 | "priority": 20, 1523 | "userSetupAllowed": false, 1524 | "autheticatorFlow": false 1525 | }, 1526 | { 1527 | "authenticator": "auth-spnego", 1528 | "authenticatorFlow": false, 1529 | "requirement": "DISABLED", 1530 | "priority": 30, 1531 | "userSetupAllowed": false, 1532 | "autheticatorFlow": false 1533 | } 1534 | ] 1535 | }, 1536 | { 1537 | "id": "0532a95e-6516-4b42-9659-0a53fcd668f0", 1538 | "alias": "Browser - Conditional OTP", 1539 | "description": "Flow to determine if the OTP is required for the authentication", 1540 | "providerId": "basic-flow", 1541 | "topLevel": false, 1542 | "builtIn": true, 1543 | "authenticationExecutions": [ 1544 | { 1545 | "authenticator": "conditional-user-configured", 1546 | "authenticatorFlow": false, 1547 | "requirement": "REQUIRED", 1548 | "priority": 10, 1549 | "userSetupAllowed": false, 1550 | "autheticatorFlow": false 1551 | }, 1552 | { 1553 | "authenticator": "auth-otp-form", 1554 | "authenticatorFlow": false, 1555 | "requirement": "REQUIRED", 1556 | "priority": 20, 1557 | "userSetupAllowed": false, 1558 | "autheticatorFlow": false 1559 | } 1560 | ] 1561 | }, 1562 | { 1563 | "id": "66c8d13d-dced-4edc-aaac-857a55c259a4", 1564 | "alias": "Direct Grant - Conditional OTP", 1565 | "description": "Flow to determine if the OTP is required for the authentication", 1566 | "providerId": "basic-flow", 1567 | "topLevel": false, 1568 | "builtIn": true, 1569 | "authenticationExecutions": [ 1570 | { 1571 | "authenticator": "conditional-user-configured", 1572 | "authenticatorFlow": false, 1573 | "requirement": "REQUIRED", 1574 | "priority": 10, 1575 | "userSetupAllowed": false, 1576 | "autheticatorFlow": false 1577 | }, 1578 | { 1579 | "authenticator": "direct-grant-validate-otp", 1580 | "authenticatorFlow": false, 1581 | "requirement": "REQUIRED", 1582 | "priority": 20, 1583 | "userSetupAllowed": false, 1584 | "autheticatorFlow": false 1585 | } 1586 | ] 1587 | }, 1588 | { 1589 | "id": "58a2c1f3-833f-4bba-bf1a-49e8452cf397", 1590 | "alias": "First broker login - Conditional OTP", 1591 | "description": "Flow to determine if the OTP is required for the authentication", 1592 | "providerId": "basic-flow", 1593 | "topLevel": false, 1594 | "builtIn": true, 1595 | "authenticationExecutions": [ 1596 | { 1597 | "authenticator": "conditional-user-configured", 1598 | "authenticatorFlow": false, 1599 | "requirement": "REQUIRED", 1600 | "priority": 10, 1601 | "userSetupAllowed": false, 1602 | "autheticatorFlow": false 1603 | }, 1604 | { 1605 | "authenticator": "auth-otp-form", 1606 | "authenticatorFlow": false, 1607 | "requirement": "REQUIRED", 1608 | "priority": 20, 1609 | "userSetupAllowed": false, 1610 | "autheticatorFlow": false 1611 | } 1612 | ] 1613 | }, 1614 | { 1615 | "id": "eebac5af-8d5e-404e-ab0f-b72a1355115a", 1616 | "alias": "Handle Existing Account", 1617 | "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", 1618 | "providerId": "basic-flow", 1619 | "topLevel": false, 1620 | "builtIn": true, 1621 | "authenticationExecutions": [ 1622 | { 1623 | "authenticator": "idp-confirm-link", 1624 | "authenticatorFlow": false, 1625 | "requirement": "REQUIRED", 1626 | "priority": 10, 1627 | "userSetupAllowed": false, 1628 | "autheticatorFlow": false 1629 | }, 1630 | { 1631 | "authenticatorFlow": true, 1632 | "requirement": "REQUIRED", 1633 | "priority": 20, 1634 | "flowAlias": "Account verification options", 1635 | "userSetupAllowed": false, 1636 | "autheticatorFlow": true 1637 | } 1638 | ] 1639 | }, 1640 | { 1641 | "id": "3c5f5843-d51a-488b-847b-c573ef3402f1", 1642 | "alias": "Reset - Conditional OTP", 1643 | "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", 1644 | "providerId": "basic-flow", 1645 | "topLevel": false, 1646 | "builtIn": true, 1647 | "authenticationExecutions": [ 1648 | { 1649 | "authenticator": "conditional-user-configured", 1650 | "authenticatorFlow": false, 1651 | "requirement": "REQUIRED", 1652 | "priority": 10, 1653 | "userSetupAllowed": false, 1654 | "autheticatorFlow": false 1655 | }, 1656 | { 1657 | "authenticator": "reset-otp", 1658 | "authenticatorFlow": false, 1659 | "requirement": "REQUIRED", 1660 | "priority": 20, 1661 | "userSetupAllowed": false, 1662 | "autheticatorFlow": false 1663 | } 1664 | ] 1665 | }, 1666 | { 1667 | "id": "669d6f7a-d0d9-4103-a8e2-dfb008c32966", 1668 | "alias": "User creation or linking", 1669 | "description": "Flow for the existing/non-existing user alternatives", 1670 | "providerId": "basic-flow", 1671 | "topLevel": false, 1672 | "builtIn": true, 1673 | "authenticationExecutions": [ 1674 | { 1675 | "authenticatorConfig": "create unique user config", 1676 | "authenticator": "idp-create-user-if-unique", 1677 | "authenticatorFlow": false, 1678 | "requirement": "ALTERNATIVE", 1679 | "priority": 10, 1680 | "userSetupAllowed": false, 1681 | "autheticatorFlow": false 1682 | }, 1683 | { 1684 | "authenticatorFlow": true, 1685 | "requirement": "ALTERNATIVE", 1686 | "priority": 20, 1687 | "flowAlias": "Handle Existing Account", 1688 | "userSetupAllowed": false, 1689 | "autheticatorFlow": true 1690 | } 1691 | ] 1692 | }, 1693 | { 1694 | "id": "5241900a-fe22-4744-b2c2-927619d0ca30", 1695 | "alias": "Verify Existing Account by Re-authentication", 1696 | "description": "Reauthentication of existing account", 1697 | "providerId": "basic-flow", 1698 | "topLevel": false, 1699 | "builtIn": true, 1700 | "authenticationExecutions": [ 1701 | { 1702 | "authenticator": "idp-username-password-form", 1703 | "authenticatorFlow": false, 1704 | "requirement": "REQUIRED", 1705 | "priority": 10, 1706 | "userSetupAllowed": false, 1707 | "autheticatorFlow": false 1708 | }, 1709 | { 1710 | "authenticatorFlow": true, 1711 | "requirement": "CONDITIONAL", 1712 | "priority": 20, 1713 | "flowAlias": "First broker login - Conditional OTP", 1714 | "userSetupAllowed": false, 1715 | "autheticatorFlow": true 1716 | } 1717 | ] 1718 | }, 1719 | { 1720 | "id": "0b6812b7-22eb-4b3c-b7f6-d2a1dcbbc0a6", 1721 | "alias": "browser", 1722 | "description": "browser based authentication", 1723 | "providerId": "basic-flow", 1724 | "topLevel": true, 1725 | "builtIn": true, 1726 | "authenticationExecutions": [ 1727 | { 1728 | "authenticator": "auth-cookie", 1729 | "authenticatorFlow": false, 1730 | "requirement": "ALTERNATIVE", 1731 | "priority": 10, 1732 | "userSetupAllowed": false, 1733 | "autheticatorFlow": false 1734 | }, 1735 | { 1736 | "authenticator": "auth-spnego", 1737 | "authenticatorFlow": false, 1738 | "requirement": "DISABLED", 1739 | "priority": 20, 1740 | "userSetupAllowed": false, 1741 | "autheticatorFlow": false 1742 | }, 1743 | { 1744 | "authenticator": "identity-provider-redirector", 1745 | "authenticatorFlow": false, 1746 | "requirement": "ALTERNATIVE", 1747 | "priority": 25, 1748 | "userSetupAllowed": false, 1749 | "autheticatorFlow": false 1750 | }, 1751 | { 1752 | "authenticatorFlow": true, 1753 | "requirement": "ALTERNATIVE", 1754 | "priority": 30, 1755 | "flowAlias": "forms", 1756 | "userSetupAllowed": false, 1757 | "autheticatorFlow": true 1758 | } 1759 | ] 1760 | }, 1761 | { 1762 | "id": "3e550a4b-7c0e-492c-893a-bf79cd17d97e", 1763 | "alias": "clients", 1764 | "description": "Base authentication for clients", 1765 | "providerId": "client-flow", 1766 | "topLevel": true, 1767 | "builtIn": true, 1768 | "authenticationExecutions": [ 1769 | { 1770 | "authenticator": "client-secret", 1771 | "authenticatorFlow": false, 1772 | "requirement": "ALTERNATIVE", 1773 | "priority": 10, 1774 | "userSetupAllowed": false, 1775 | "autheticatorFlow": false 1776 | }, 1777 | { 1778 | "authenticator": "client-jwt", 1779 | "authenticatorFlow": false, 1780 | "requirement": "ALTERNATIVE", 1781 | "priority": 20, 1782 | "userSetupAllowed": false, 1783 | "autheticatorFlow": false 1784 | }, 1785 | { 1786 | "authenticator": "client-secret-jwt", 1787 | "authenticatorFlow": false, 1788 | "requirement": "ALTERNATIVE", 1789 | "priority": 30, 1790 | "userSetupAllowed": false, 1791 | "autheticatorFlow": false 1792 | }, 1793 | { 1794 | "authenticator": "client-x509", 1795 | "authenticatorFlow": false, 1796 | "requirement": "ALTERNATIVE", 1797 | "priority": 40, 1798 | "userSetupAllowed": false, 1799 | "autheticatorFlow": false 1800 | } 1801 | ] 1802 | }, 1803 | { 1804 | "id": "43c25eac-1058-464f-8f05-98ea843eba3b", 1805 | "alias": "direct grant", 1806 | "description": "OpenID Connect Resource Owner Grant", 1807 | "providerId": "basic-flow", 1808 | "topLevel": true, 1809 | "builtIn": true, 1810 | "authenticationExecutions": [ 1811 | { 1812 | "authenticator": "direct-grant-validate-username", 1813 | "authenticatorFlow": false, 1814 | "requirement": "REQUIRED", 1815 | "priority": 10, 1816 | "userSetupAllowed": false, 1817 | "autheticatorFlow": false 1818 | }, 1819 | { 1820 | "authenticator": "direct-grant-validate-password", 1821 | "authenticatorFlow": false, 1822 | "requirement": "REQUIRED", 1823 | "priority": 20, 1824 | "userSetupAllowed": false, 1825 | "autheticatorFlow": false 1826 | }, 1827 | { 1828 | "authenticatorFlow": true, 1829 | "requirement": "CONDITIONAL", 1830 | "priority": 30, 1831 | "flowAlias": "Direct Grant - Conditional OTP", 1832 | "userSetupAllowed": false, 1833 | "autheticatorFlow": true 1834 | } 1835 | ] 1836 | }, 1837 | { 1838 | "id": "84f1f90a-74f5-49f1-9d84-cf88a7a33ad2", 1839 | "alias": "docker auth", 1840 | "description": "Used by Docker clients to authenticate against the IDP", 1841 | "providerId": "basic-flow", 1842 | "topLevel": true, 1843 | "builtIn": true, 1844 | "authenticationExecutions": [ 1845 | { 1846 | "authenticator": "docker-http-basic-authenticator", 1847 | "authenticatorFlow": false, 1848 | "requirement": "REQUIRED", 1849 | "priority": 10, 1850 | "userSetupAllowed": false, 1851 | "autheticatorFlow": false 1852 | } 1853 | ] 1854 | }, 1855 | { 1856 | "id": "06f59fb0-ecfc-4aa9-a47c-803c308b2f71", 1857 | "alias": "first broker login", 1858 | "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", 1859 | "providerId": "basic-flow", 1860 | "topLevel": true, 1861 | "builtIn": true, 1862 | "authenticationExecutions": [ 1863 | { 1864 | "authenticatorConfig": "review profile config", 1865 | "authenticator": "idp-review-profile", 1866 | "authenticatorFlow": false, 1867 | "requirement": "REQUIRED", 1868 | "priority": 10, 1869 | "userSetupAllowed": false, 1870 | "autheticatorFlow": false 1871 | }, 1872 | { 1873 | "authenticatorFlow": true, 1874 | "requirement": "REQUIRED", 1875 | "priority": 20, 1876 | "flowAlias": "User creation or linking", 1877 | "userSetupAllowed": false, 1878 | "autheticatorFlow": true 1879 | } 1880 | ] 1881 | }, 1882 | { 1883 | "id": "bcc6b5be-6b35-4ae9-9059-5c6c7f8e30a7", 1884 | "alias": "forms", 1885 | "description": "Username, password, otp and other auth forms.", 1886 | "providerId": "basic-flow", 1887 | "topLevel": false, 1888 | "builtIn": true, 1889 | "authenticationExecutions": [ 1890 | { 1891 | "authenticator": "auth-username-password-form", 1892 | "authenticatorFlow": false, 1893 | "requirement": "REQUIRED", 1894 | "priority": 10, 1895 | "userSetupAllowed": false, 1896 | "autheticatorFlow": false 1897 | }, 1898 | { 1899 | "authenticatorFlow": true, 1900 | "requirement": "CONDITIONAL", 1901 | "priority": 20, 1902 | "flowAlias": "Browser - Conditional OTP", 1903 | "userSetupAllowed": false, 1904 | "autheticatorFlow": true 1905 | } 1906 | ] 1907 | }, 1908 | { 1909 | "id": "fc3a8fb5-80d9-4c1a-9105-387b44f95085", 1910 | "alias": "http challenge", 1911 | "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", 1912 | "providerId": "basic-flow", 1913 | "topLevel": true, 1914 | "builtIn": true, 1915 | "authenticationExecutions": [ 1916 | { 1917 | "authenticator": "no-cookie-redirect", 1918 | "authenticatorFlow": false, 1919 | "requirement": "REQUIRED", 1920 | "priority": 10, 1921 | "userSetupAllowed": false, 1922 | "autheticatorFlow": false 1923 | }, 1924 | { 1925 | "authenticatorFlow": true, 1926 | "requirement": "REQUIRED", 1927 | "priority": 20, 1928 | "flowAlias": "Authentication Options", 1929 | "userSetupAllowed": false, 1930 | "autheticatorFlow": true 1931 | } 1932 | ] 1933 | }, 1934 | { 1935 | "id": "ac50dd52-acfd-4e3e-a0ae-315e8263af2e", 1936 | "alias": "registration", 1937 | "description": "registration flow", 1938 | "providerId": "basic-flow", 1939 | "topLevel": true, 1940 | "builtIn": true, 1941 | "authenticationExecutions": [ 1942 | { 1943 | "authenticator": "registration-page-form", 1944 | "authenticatorFlow": true, 1945 | "requirement": "REQUIRED", 1946 | "priority": 10, 1947 | "flowAlias": "registration form", 1948 | "userSetupAllowed": false, 1949 | "autheticatorFlow": true 1950 | } 1951 | ] 1952 | }, 1953 | { 1954 | "id": "e2137dc5-70bd-4c7b-bc09-51f328dd9ba3", 1955 | "alias": "registration form", 1956 | "description": "registration form", 1957 | "providerId": "form-flow", 1958 | "topLevel": false, 1959 | "builtIn": true, 1960 | "authenticationExecutions": [ 1961 | { 1962 | "authenticator": "registration-user-creation", 1963 | "authenticatorFlow": false, 1964 | "requirement": "REQUIRED", 1965 | "priority": 20, 1966 | "userSetupAllowed": false, 1967 | "autheticatorFlow": false 1968 | }, 1969 | { 1970 | "authenticator": "registration-profile-action", 1971 | "authenticatorFlow": false, 1972 | "requirement": "REQUIRED", 1973 | "priority": 40, 1974 | "userSetupAllowed": false, 1975 | "autheticatorFlow": false 1976 | }, 1977 | { 1978 | "authenticator": "registration-password-action", 1979 | "authenticatorFlow": false, 1980 | "requirement": "REQUIRED", 1981 | "priority": 50, 1982 | "userSetupAllowed": false, 1983 | "autheticatorFlow": false 1984 | }, 1985 | { 1986 | "authenticator": "registration-recaptcha-action", 1987 | "authenticatorFlow": false, 1988 | "requirement": "DISABLED", 1989 | "priority": 60, 1990 | "userSetupAllowed": false, 1991 | "autheticatorFlow": false 1992 | } 1993 | ] 1994 | }, 1995 | { 1996 | "id": "c3047132-5b25-4fd7-91d0-d882a39fa6af", 1997 | "alias": "reset credentials", 1998 | "description": "Reset credentials for a user if they forgot their password or something", 1999 | "providerId": "basic-flow", 2000 | "topLevel": true, 2001 | "builtIn": true, 2002 | "authenticationExecutions": [ 2003 | { 2004 | "authenticator": "reset-credentials-choose-user", 2005 | "authenticatorFlow": false, 2006 | "requirement": "REQUIRED", 2007 | "priority": 10, 2008 | "userSetupAllowed": false, 2009 | "autheticatorFlow": false 2010 | }, 2011 | { 2012 | "authenticator": "reset-credential-email", 2013 | "authenticatorFlow": false, 2014 | "requirement": "REQUIRED", 2015 | "priority": 20, 2016 | "userSetupAllowed": false, 2017 | "autheticatorFlow": false 2018 | }, 2019 | { 2020 | "authenticator": "reset-password", 2021 | "authenticatorFlow": false, 2022 | "requirement": "REQUIRED", 2023 | "priority": 30, 2024 | "userSetupAllowed": false, 2025 | "autheticatorFlow": false 2026 | }, 2027 | { 2028 | "authenticatorFlow": true, 2029 | "requirement": "CONDITIONAL", 2030 | "priority": 40, 2031 | "flowAlias": "Reset - Conditional OTP", 2032 | "userSetupAllowed": false, 2033 | "autheticatorFlow": true 2034 | } 2035 | ] 2036 | }, 2037 | { 2038 | "id": "bf231912-1a75-4a51-9c2b-a4c129c0ffe8", 2039 | "alias": "saml ecp", 2040 | "description": "SAML ECP Profile Authentication Flow", 2041 | "providerId": "basic-flow", 2042 | "topLevel": true, 2043 | "builtIn": true, 2044 | "authenticationExecutions": [ 2045 | { 2046 | "authenticator": "http-basic-authenticator", 2047 | "authenticatorFlow": false, 2048 | "requirement": "REQUIRED", 2049 | "priority": 10, 2050 | "userSetupAllowed": false, 2051 | "autheticatorFlow": false 2052 | } 2053 | ] 2054 | } 2055 | ], 2056 | "authenticatorConfig": [ 2057 | { 2058 | "id": "b0028fff-6f74-4b64-a4e8-62586b4bf9dc", 2059 | "alias": "create unique user config", 2060 | "config": { 2061 | "require.password.update.after.registration": "false" 2062 | } 2063 | }, 2064 | { 2065 | "id": "2663a61b-8dc7-4d17-9c5c-31a86c17eac3", 2066 | "alias": "review profile config", 2067 | "config": { 2068 | "update.profile.on.first.login": "missing" 2069 | } 2070 | } 2071 | ], 2072 | "requiredActions": [ 2073 | { 2074 | "alias": "CONFIGURE_TOTP", 2075 | "name": "Configure OTP", 2076 | "providerId": "CONFIGURE_TOTP", 2077 | "enabled": true, 2078 | "defaultAction": false, 2079 | "priority": 10, 2080 | "config": {} 2081 | }, 2082 | { 2083 | "alias": "terms_and_conditions", 2084 | "name": "Terms and Conditions", 2085 | "providerId": "terms_and_conditions", 2086 | "enabled": false, 2087 | "defaultAction": false, 2088 | "priority": 20, 2089 | "config": {} 2090 | }, 2091 | { 2092 | "alias": "UPDATE_PASSWORD", 2093 | "name": "Update Password", 2094 | "providerId": "UPDATE_PASSWORD", 2095 | "enabled": true, 2096 | "defaultAction": false, 2097 | "priority": 30, 2098 | "config": {} 2099 | }, 2100 | { 2101 | "alias": "UPDATE_PROFILE", 2102 | "name": "Update Profile", 2103 | "providerId": "UPDATE_PROFILE", 2104 | "enabled": true, 2105 | "defaultAction": false, 2106 | "priority": 40, 2107 | "config": {} 2108 | }, 2109 | { 2110 | "alias": "VERIFY_EMAIL", 2111 | "name": "Verify Email", 2112 | "providerId": "VERIFY_EMAIL", 2113 | "enabled": true, 2114 | "defaultAction": false, 2115 | "priority": 50, 2116 | "config": {} 2117 | }, 2118 | { 2119 | "alias": "delete_account", 2120 | "name": "Delete Account", 2121 | "providerId": "delete_account", 2122 | "enabled": false, 2123 | "defaultAction": false, 2124 | "priority": 60, 2125 | "config": {} 2126 | }, 2127 | { 2128 | "alias": "update_user_locale", 2129 | "name": "Update User Locale", 2130 | "providerId": "update_user_locale", 2131 | "enabled": true, 2132 | "defaultAction": false, 2133 | "priority": 1000, 2134 | "config": {} 2135 | } 2136 | ], 2137 | "browserFlow": "browser", 2138 | "registrationFlow": "registration", 2139 | "directGrantFlow": "direct grant", 2140 | "resetCredentialsFlow": "reset credentials", 2141 | "clientAuthenticationFlow": "clients", 2142 | "dockerAuthenticationFlow": "docker auth", 2143 | "attributes": { 2144 | "cibaBackchannelTokenDeliveryMode": "poll", 2145 | "cibaExpiresIn": "120", 2146 | "cibaAuthRequestedUserHint": "login_hint", 2147 | "oauth2DeviceCodeLifespan": "600", 2148 | "oauth2DevicePollingInterval": "5", 2149 | "parRequestUriLifespan": "60", 2150 | "cibaInterval": "5" 2151 | }, 2152 | "keycloakVersion": "15.0.2", 2153 | "userManagedAccessAllowed": false, 2154 | "clientProfiles": { 2155 | "profiles": [] 2156 | }, 2157 | "clientPolicies": { 2158 | "policies": [] 2159 | } 2160 | } --------------------------------------------------------------------------------