# Keycloak

This tutorial shows you how you can secure your Ts.ED application with an existing Keycloak instance.

# Installation

Before securing the application with Keycloak, we need to install the Keycloak Node.js Adapter (opens new window) and Express-Session (opens new window) modules.

Note

The version of the keycloak-connect module should be the same version as your Keycloak instance.

npm install --save keycloak-connect
npm install --save express-session
npm install --save-dev @types/keycloak-connect
npm install --save-dev @types/express-session
1
2
3
4

# Download keycloak.json

Put the keycloak.json file for your Keycloak client to src/config/keycloak.

How exactly the file is downloaded can be found in the official Keycloak documentation (opens new window).

# KeycloakService

Create a KeycloakService in src/services that handles the memory store and the Keycloak instance.

import {Service} from '@tsed/di';
import {MemoryStore} from 'express-session';
import {$log} from "@tsed/common";
import KeycloakConnect = require('keycloak-connect');

@Service()
export class KeycloakService {

    private keycloak: KeycloakConnect.Keycloak;
    private memoryStore: MemoryStore;

    constructor() {
        this.initKeycloak();
    }

    public initKeycloak(): KeycloakConnect.Keycloak {
        if (this.keycloak) {
            $log.warn('Trying to init Keycloak again!');
            return this.keycloak;
        } else {
            $log.info('Initializing Keycloak...');
            this.memoryStore = new MemoryStore();
            this.keycloak = new KeycloakConnect(
                { store: this.memoryStore },
                'src/config/keycloak/keycloak.json'
            );
            return this.keycloak;
        }
    }

    public getKeycloakInstance(): KeycloakConnect.Keycloak {
        return this.keycloak;
    }

    public getMemoryStore(): MemoryStore {
        return this.memoryStore;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# Add KeycloakService to Server

Make sure that the KeycloakService is part of the componentsScan array of the global configuration.

The KeycloakService can then be injected in the Server class and the middleware of express-session and keycloak-connect can be called.

export class Server {
    @Inject()
    app: PlatformApplication;

    @Inject()
    keycloakService: KeycloakService;

    @Configuration()
    settings: Configuration;

    $beforeRoutesInit(): void {
        this.app
            .use(cors())
            .use(cookieParser())
            .use(compress({}))
            .use(methodOverride())
            .use(bodyParser.json())
            .use(bodyParser.urlencoded({
                extended: true
            }))
            .use(session({
                secret: 'some secret',
                resave: false,
                saveUninitialized: true,
                store: this.keycloakService.getMemoryStore()
            }))
            .use(this.keycloakService.getKeycloakInstance().middleware());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# KeycloakMiddleware

To secure your routes add a KeycloakMiddleware class to src/middlewares.

import {Context, IMiddleware, Inject, Middleware} from '@tsed/common';
import {KeycloakAuthOptions} from '../decorators/KeycloakAuthDecorator';
import {KeycloakService} from '../services/KeycloakService';

@Middleware()
export class KeycloakMiddleware implements IMiddleware {

    @Inject()
    keycloakService: KeycloakService;

    public use(@Context() ctx: Context) {
        const options: KeycloakAuthOptions = ctx.endpoint.store.get(KeycloakMiddleware);
        const keycloak = this.keycloakService.getKeycloakInstance();
        return keycloak.protect(options.role);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# KeycloakAuthDecorator

To protect certain routes create a KeycloakAuthDecorator at src/decorators.

import {Returns} from '@tsed/schema';
import {IAuthOptions, UseAuth} from '@tsed/common';
import {useDecorators} from '@tsed/core';
import {Security} from '@tsed/schema';
import {KeycloakMiddleware} from '../middlewares/KeycloakMiddleware';

export interface KeycloakAuthOptions extends IAuthOptions {
    role?: string;
    scopes?: string[];
}

export function KeycloakAuth(options: KeycloakAuthOptions = {}): Function {
    return useDecorators(
        UseAuth(KeycloakMiddleware, options),
        Security('oauth2', ...(options.scopes || [])),
        Returns(403)
    );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# Protecting routes role-based in a controller

Now we can protect routes with our custom KeycloakAuth decorator.

import {Controller, Get} from "@tsed/common";
import {KeycloakAuth} from '../decorators/KeycloakAuthDecorator';

@Controller("/hello-world")
export class HelloWorldController {
  @Get("/")
  @KeycloakAuth({ role: "realm:example-role" })
  get() {
    return "hello";
  }
}
1
2
3
4
5
6
7
8
9
10
11

# Author

    Last Updated: 4/12/2021, 7:40:30 AM

    Other topics