• by Mohammed Faseem
  • Senior Software Engineer

Identity and Access Management (IAM) in Microservices using Keycloak

Securing an enterprise application is a key aspect of modern enterprise application development. There are plenty of mechanisms to secure such applications, especially in distributed environments. Identity and Access Management (IAM) system is one of those very popular security components in a modern day distributed application. Concepts such as Identity Federation, Single Sign On (SSO) and Identity Provisioning are some of those key concepts, which are coupled with IAM systems.

What is Keycloak?

Keycloak is a popular “open source” IAM solutions in the market, which is aimed at securing modern enterprise applications and services. It makes it easy to secure applications and services with little to no code [1].

An IAM solution means, it can provide authentication to your applications as well as helps to manage the users and their access levels / roles / permissions. Furthermore, it has other built-in features like User Federation, Single Sign On (SSO), Centralized Identity Management, Standard Protocols, Client Adapters, Identity brokering and high performance.

How Keycloak Works

Keycloak is completely a separate server that runs in a different place or a different port. Our applications are configured to be secured by this independent server. Keycloak uses open protocol standards like Open ID Connect or SAML 2.0, especially in Identity Federation and SSO scenarios.

In our browser, when we try to access applications, it redirects the browser from the application to the Keycloak authentication server where they enter their credentials. This is important because users are completely isolated from applications and applications never see user credentials. Instead, applications are given an identity token or assertion that is cryptographically signed. These tokens can identify information such as user name, address, email, and other profile data. They can also hold permission data so that applications can make authorization decisions. It can also be used to make secure invocations on REST-based services [1].

If the user credentials provided are correct, the Keycloak Identity Server generates a token and gives it back to the application. If the token is valid, the user is able to continue with the rest of the tasks. When it comes to the context of Microservices architecture, the role of Keycloak is unimaginable with its facility of SSO.

Keycloak in a Microservices Environment

One of Acentura’s key Telco clients uses Keycloak as its IAM solution for one of their Microservices environments. There are around 40 Microservices, which are federated through a centralized Keycloak environment.

The following diagram shows the message flow in this system:

Steps:

  •  The user requests the front-end application (an Angular application).
  •  The front-end application is redirected to the Keycloak server for authentication.
    •  Apps are launched automatically on startup to immerse your users in the app experience
    •  If the authentication is unsuccessful, the user will be redirected to the Keycloak server’s login page for reauthentication
  •  With the provided JWT, the front-end application will make a service (REST) request to
  •  When a Microservice is accessed with a REST request, it will pass the token that’s in the request header to the Keycloak server.
  •  The Keycloak server will notify the Microservice if the token is valid or not
  •  If the token is valid, the Microservice will serve that request accordingly
  •  If the token is invalid, the Microservice will not serve the request and will respond with a corresponding error response

This is how a typical JWT, generated by the Keycloak server, looks like. We will notice that this JWT contains all the information about the application user and its Keycloak configurations.

The decrypted form is:

{
                                            "jti": "e994fc9e-e96e-49ac-ab71-7a2a917674df",
                                "exp": 1533104336,  // Wed Aug 01 2018 11:48:56 GMT+0530 (India Standard Time)
                                "nbf": 0,
                                "iat": 1533104036,  // Wed Aug 01 2018 11:43:56 GMT+0530 (India Standard Time)
                                "iss": "http://localhost:8080/auth/realms/Aplos",
                                "aud": "aplos-frontend",
                                "sub": "8562a2ac-ca92-4741-812a-3557d12da057",
                                "typ": "Bearer",
                                "azp": "aplos-frontend",
                                "nonce": "0ffec321-b829-45e5-83cf-9ab205fc8103",
                                "auth_time": 1533097554,  // Wed Aug 01 2018 09:55:54 GMT+0530 (India Standard Time)
                                "session_state": "2b70a773-42f9-4ebb-b8f6-135150f1486a",
                                "acr": "0",
                                "allowed-origins": [
                                    "http://localhost:4000"
                                ],
                                "realm_access": {
                                    "roles": [
                                    "offline_access",
                                    "uma_authorization"
                                    ]
                                },
                                "resource_access": {
                                    "account": {
                                    "roles": [
                                        "manage-account",
                                        "manage-account-links",
                                        "view-profile"
                                    ]
                                    }
                                },
                                "scope": "openid profile email",
                                "email_verified": false,
                                "name": "FSM MHD",
                                "preferred_username": "fsm",
                                "given_name": "FSM",
                                "family_name": "MHD",
                                "email": "fsm@aplos.com"
                                }

The Demo – Keycloak with Microservices

Follow the steps given below in order to develop a simple Angular Spring Boot Microservice, which can be authenticated by a Keycloak server.

This will start the server and it will be running on port

  •  1.0 Setting up the Keycloak Server (Keycloak-4.1.0.Final)
  •  1.1 Download the latest stable Keycloak server standalone.sh (Linux/Mac) or standalone.bat (Windows) In the bin directory,
  •  1.3 If the server is started successfully, visit localhost:8080/auth. If you are doing this for the first time, in the browser it will ask you to register as a master user of the Keycloak server. If not, you will be directly redirected to the welcome page.
  •  1.4 Creating a Custom

What is a Realm?

A realm secures and manages security meta data for a set of users, applications, and registered OAuth clients. Users can be created confined to a specific realm within the Administration console. Roles can be defined at the realm level. You can also set up user role mappings to assign these permissions to specific users. [1]

Create a new realm by hovering over the "Master" realm and then click on the "Add realm" button. Provide a name for it and create the realm. Once this is done, you will be directed to a page to configure the realm. Let’s Name it “Test-Realm”.

In this page, we can configure the Realm Settings. For the moment, let’s keep it as is. On the left side bar, we can set up clients, roles, users, etc.

We will be using this single client to serve the request of both front-end and back-end requests. If we want, we can have separate clients for each.

 1.5 Creating a Client

Clients are entities that can request authentication of a user. Clients come in two forms. The first type of client is an application that wants to participate in Single-Sign-On (SSO). These clients just want Keycloak to provide security for them. The other type of client is one that is requesting an access token so that it can invoke other services on behalf of the authenticated user. [1]

Our front- end application will ask Keycloak to generate the token and the back-end application will grant access to the resources if and only if the token is valid. Let’s name it “test-client” and keep another configuration as default. We will be navigated to the following page.

Let’s name it “test-client” and keep another configuration as default. We will be navigated to the following page.

In the client settings page, we will configure only: Valid Redirect URIs as http://localhost:4200/*

Web Origins as http://localhost:4200

Other settings will remain as default.

 

Now we’ve configured a client successfully.

 1.6 Configuring Roles

By clicking on Roles in the left menu bar we can set up roles in our system. Once you visit the page, existing roles will be loaded. There are always two existing roles “offline_access” and “uma_authorization”. Let’s keep this as is and create our own roles.

Create two roles “admin” and “user”.

 1.7 Configuring Users

Once a user has been created, we will be directed to the settings page relevant to the user.

Here we can configure the credentials as well as the role settings.

In the role mapping tab, we will assign both user and admin roles to the admin user, and only the user role for a normal_user.

Now everything has been set up in Keycloak.

  •  We have started the Keycloak server
  •  Set up a Realm
  •  Set up a client for the Realm
  •  Set up roles
  •  Set up users and credential as well as the roles for the users

2.0 Setting up the Angular Front-end

 2.1 Software Installation

Install node, npm and Angular CLI in order` to generate the Angular CLI project. To begin with, let’s focus on creating the application.

                                    Angular CLI: 1.7.4
                                    Node: 8.11.1
                                    

Run the following command to generate the project: ng new test-angular

 2.2 Provide a Keycloak Adaptor

What are Client Adapters?

Keycloak client adapters are libraries that make it very easy to secure applications and services with Keycloak. We call these ‘adapters’ rather than libraries as they provide a tight integration to the underlying platform and framework. This makes adapters easy to use and require less boilerplate code than what is typically required by a library. [1]

Now include the following line in the index.html file:

 <-script src="//localhost:8080/auth/js/keycloak.js"><-/script>
                                    

This will download the adapter file from running Keycloak server at the start of the application.

 2.3 Include the Keycloak Service File The Folder Structure will be as follows:

 2.4 Add the KeycloakService.ts file

The KeycloakService.ts file does the following:

- Handles the process of authorizing the user

- Connects to the KeyCloak server to get the token and user information and stores it in the application to use wherever needed.

We will have the init() method in the file which will get the Keycloak server information from the environment file and complete the login. In the event of a successful login, it will fetch a JWT as well.

Whenever a new http request is made, the getToken() method will be accessed and if you are logged in, it will obtain a token from the Keycloak server.

static init(): Promise< - any - > {
                                        const keycloakAuth: any = Keycloak({
                                            url: environment.KEYCLOAK_URL,
                                            realm: environment.KEYCLOAK_REALM,
                                            clientId: environment.KEYCLOAK_CLIENTID
                                        });
                                
                                        KeycloakService.auth.loggedIn = false;
                                
                                        return new Promise((resolve, reject) => {
                                            keycloakAuth
                                                .init({ onLoad: 'login-required' })
                                                .success(() => {
                                                    KeycloakService.auth.loggedIn = true;
                                                    KeycloakService.auth.authz = keycloakAuth;
                                                    KeycloakService.auth.logoutUrl =
                                                        keycloakAuth.authServerUrl +
                                                        '/realms/' +
                                                        environment.KEYCLOAK_REALM +
                                                        '/protocol/openid-connect/logout?redirect_uri=' +
                                                        document.baseURI;
                                
                                                    KeycloakService.auth.authz.loadUserProfile().success(data => {
                                                        this.user = new User();
                                                        this.user.username = data.username;
                                                        this.user.firstName = data.firstName;
                                                        this.user.lastName = data.lastName;
                                                        this.user.email = data.email;
                                
                                                        resolve();
                                                    });
                                                })
                                                .error(() => {
                                                    reject();
                                                });
                                        });
                                    }
                                }
                                

We will store the Keycloak server details in the environment file, to connect to the server and fetch information. We need to also provide the following information in the environment file:

                                                    export const environment = {
                                        production: false,
                                        KEYCLOAK_URL: 'http://localhost:8080/auth',
                                        KEYCLOAK_REALM: 'test-realm',
                                        KEYCLOAK_CLIENTID: 'test-client',
                                        BACKEND_URL: 'http://localhost:8000/api/'
                                    };  
                                

Note that our backend root url is also provided here itself.

When the Keycloak.init() method is invoked, we can fetch the user details and the getToken() method is invoked. Then it will update the token with the time period given in the configuration.

When the logout() method is invoked, we set the Keycloak authentication to null and will be redirected to the login page of the respective Keycloak Realm.

When the hasRole(string array) is invoked, it will return a Boolean value by checking whether the logged in user has the role or not.

 2.5 Adding an http Interceptor Service (KeycloakHttp.ts)

This will extend the standard Angular Http service. Its purpose is to include the authorization token of a logged-in user to every single Http request automatically. That means every request made with the Http service will get the "Bearer" authorization header automatically.

                                                @Injectable()
                                    export class KeycloakHttp extends Http {
                                        constructor(_backend: ConnectionBackend, _defaultOptions: RequestOptions, private _keycloakService: KeycloakService) {
                                            super(_backend, _defaultOptions);
                                        }
                                    
                                        request(url: string | Request, options?: RequestOptionsArgs): Observable<-Response-> {
                                            const tokenPromise: Promise<-string-> = this._keycloakService.getToken();
                                            const tokenObservable: Observable<-string-> = Observable.fromPromise(tokenPromise);
                                    
                                            if (typeof url === 'string') {
                                                return tokenObservable
                                                    .map(token => {
                                                        const authOptions = new RequestOptions({
                                                            headers: new Headers({
                                                                Authorization: 'Bearer ' + token
                                                            })
                                                        });
                                                        return new RequestOptions().merge(options).merge(authOptions);
                                                    })
                                                    .concatMap(opts => super.request(url, opts));
                                            } else if (url instanceof Request) {
                                                return tokenObservable
                                                    .map(token => {
                                                        url.headers.set('Authorization', 'Bearer ' + token);
                                                        return url;
                                                    })
                                                    .concatMap(request => super.request(request));
                                            }
                                        }
                                    }
                                    
                                    export function keycloakHttpFactory(backend: XHRBackend, defaultOptions: RequestOptions, keycloakService: KeycloakService) {
                                        return new KeycloakHttp(backend, defaultOptions, keycloakService);
                                    }
                                    
                                    export const KEYCLOAK_HTTP_PROVIDER = {
                                        provide: Http,
                                        useFactory: keycloakHttpFactory,
                                        deps: [XHRBackend, RequestOptions, KeycloakService]
                                    };
                                

 2.6 A REST service which has methods to do back-end calls. Let’s name it RestService.ts. This contains the REST service methods.

This consists of the following methods: getAllStaff(), getAllStudents(), getStudentById(id) and getStaffById(id)

Let’s assume that the staff resources can be accessed only by the admin users.

 2.7 Change the main.ts file to bootstrap our application module only if the authentication credentials are correct.

                                                    KeycloakService.init()
                                    .then(() => {
                                        if (KeycloakService.auth.loggedIn) {
                                        platformBrowserDynamic().bootstrapModule(AppModule);
                                        } else {
                                        KeycloakService.init();
                                        }
                                    })
                                    .catch((e: string) => {
                                        console.log('Error Logging : ' + e);
                                    });
                                

Now we will call the init() method of the Keycloak service class and if the app has been successfully logged in to, we will bootstrap our app to load in the browser, and if not Keycloak will automatically redirect the browser to the default login page of the Keycloak server.

Everything is now set in the Angular end.

  •  Club House Dolphin Menu PDF Edited
  •  Written a Keycloak service class to handle the authentication request
  •  Written a http interceptor which intercepts with all http requests and attach tokens
  •  Written a REST service to access the resources
  •  Arranged a view in app.component,html

3.0 Setting up the Spring Boot Back-end

 3.1 Create a Spring Boot Java Maven project in start.spring.io with the dependencies Web, Security and Keycloak

 3.2 Write a simple REST Controller

                                                @RestController
                                        @RequestMapping("/api")
                                        public class MyTestRestController {
                                        
                                            static Map staffMap = new HashMap<>();
                                            static Map studMap = new HashMap<>();
                                        
                                            @RequestMapping(value = "staff/getAll", method = RequestMethod.GET)
                                            public List getAllStaff() {
                                                return new ArrayList(staffMap.values());
                                            }
                                        
                                            @RequestMapping(value = "stud/getAll", method = RequestMethod.GET)
                                            public List getAllStud() {
                                                return new ArrayList(studMap.values());
                                            }
                                        
                                            @RequestMapping(value = "staff/getStaffById", method = RequestMethod.GET)
                                            public String getStaffById(@RequestParam("staffId") Integer staffId) {
                                                return staffMap.get(staffId);
                                            }
                                        
                                            @RequestMapping(value = "stud/getStudById", method = RequestMethod.GET)
                                            public String getStudById(@RequestParam("studId") Integer studId) {
                                                return studMap.get(studId);
                                            }
                                        
                                            @PostConstruct
                                            public void init() {
                                                staffMap.put(1,"Staff 1");
                                                staffMap.put(2,"Staff 2");
                                                staffMap.put(3,"Staff 3");
                                                staffMap.put(4,"Staff 4");
                                                staffMap.put(5,"Staff 5");
                                                studMap.put(1,"Stud 1");
                                                studMap.put(2,"Stud 2");
                                                studMap.put(3,"Stud 3");
                                                studMap.put(4,"Stud 4");
                                                studMap.put(5,"Stud 5");
                                        
                                        
                                            }
                                        }
                                

This class has the @PostConstruct method to initialize a data set of students and staff

 3.3 Configuring security protocol

Here we have added both spring security and Keycloak as dependencies so our Keycloak security configurations will work on top of the spring security. Let’s add the following class to configure the security:

                                                @Configuration
                                    @EnableWebSecurity
                                    @ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
                                    class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
                                    
                                        @Autowired
                                        public void configureGlobal(
                                                AuthenticationManagerBuilder auth) throws Exception {
                                    
                                            KeycloakAuthenticationProvider keycloakAuthenticationProvider
                                                    = keycloakAuthenticationProvider();
                                            keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(
                                                    new SimpleAuthorityMapper());
                                            auth.authenticationProvider(keycloakAuthenticationProvider);
                                        }
                                    
                                        @Bean
                                        public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
                                            return new KeycloakSpringBootConfigResolver();
                                        }
                                    
                                        @Bean
                                        @Override
                                        protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
                                            return new RegisterSessionAuthenticationStrategy(
                                                    new SessionRegistryImpl());
                                        }
                                    
                                        @Override
                                        protected void configure(HttpSecurity http) throws Exception {
                                            super.configure(http);
                                    
                                            http.authorizeRequests()
                                                    .antMatchers("/api/staff/*")
                                                    .hasRole("admin")
                                                    .anyRequest()
                                                    .permitAll();
                                    
                                            http.authorizeRequests()
                                                    .antMatchers("/api/student/*")
                                                    .hasRole("user")
                                                    .anyRequest()
                                                    .permitAll();
                                    
                                        }
                                    }
                                

Here we have added both spring security and Keycloak as dependencies so our Keycloak security configurations will work on top of the spring security. Let’s add the following class to configure the security:

 3.4

Create application.yml file in the resources folder to configure our application to look for the Keycloak server and fetch information. Our application will run on port 8001 as the Keycloak server is running on port 8080.

                                                server:
                                    port : 8001
                                    
                                    keycloak:
                                    realm : Test-Realm
                                    auth-server-url : http://localhost:8080/auth
                                    ssl-required : external
                                    resource : test-client
                                    cors : true
                                

In addition to the server port settings, we have to configure the Keycloak server properties as well in order to fetch information from the Keycloak server.

Now staff/ resources can be accessed only by the admin role users and the user should have the user role to access the student/ resources.

Everything is ready in the Spring Boot back-end.

  • 1) Created a Spring Boot app with the mentioned dependencies
  • 2) Written a REST controller class to handle the http REST req
  • 3) Written a Security configuration to apply our security constraints
  • 4) Created application.yml to provide Keycloak info and setup server port

The example explained above can be downloaded from the links given under the Resources section (see below).

Happy Keycloaking

Resources:

Both Angular and Spring boot projects can be downloaded from the following github locations.

Front-end : https://github.com/Faseem/Keycloak-test-FE

Back-end : https://github.com/Faseem/Keycloak-test

References : Keycloak official website: https://www.keycloak.org/

Visit us on