
Identity and Access Management Solution
Abdullah Fathi


IdentiTY & aCCESS MANAGEMENT
- Management of users and their privileges for accessing a system
 - Perform authentication & authorization
 - Many systems depend on an IAM system
 
Introduction
- Open Source identity and access management solution
 - Started in 2013, broad adoption since 2015
 - Vital Community with 470+ Contributors 3.3k+ Forks
 - Very robust, good documentation, many examples
 - SSO solution designed for Modern Applications, APIs and Services
 - Web Based GUI
 

Introduction
- Open Source software product that allows single sign-on with identity and access management aimed at modern applications and services
 - Support multiple protocols such as OpenID Connect, OAuth 2.0 and SAML 2.0.
 - Provides features such as user federation, social login, multifactor authentication and authorization services
 

Why Keycloak?
- Delegate your security
 - User registration and Login
 - Deal with login forms and other related forms
 - Developer can focus on core technology
 - Uses best practices
 - Centralized user management
 - SSO, Social Identity Providers and Federation
 

Keycloak Architecture
OpenID Connect / OAuth 2.0
- JSON
 - Simple
 - Bearer Token
 
When?
- Default
 - SPA, Mobile
 - REST services
 
SAML v2
- XML
 - More Mature
 - More Complex
 
When?
- Monoliths
 - Apps with SAML Support
 - If you have some fancy requirements
 
User Storage SPI

KEYCLOAK TERMS
1) Realms
- Manage entities such as users, roles, clients, etc.
 - Each realms is fully isolated with other realms
 

2) Clients
- Entities that can request authentication of a user
 - Type of Clients:
	
- Type 1: An application that wants to participate in single-sign-on
 - Type 2: An application that requests an access token so that it can invoke other services on behalf of the authenticated user
 
 - Client Communication Protocols:
	
- OpenID Connect (OIDC): preferred way since it was designed to be web friendly and work well with HTML5 & Javascript applications.
 - SAML: Mature protocol but it tends to be bit more verbose than OIDC
 
 
3) client scopes
- Allows creating a re-usable groups of claims that are added to token issued to a client
 

4) Roles
- Identify a type or category of a user
 - One user can have multiple roles
 - Type of Roles
	
- Realm Roles
		
- Realm level roles
 - Can be defined as a global namespace to define roles
 
 - Client Roles
		
- Client level roles
 - This is a namespace dedicated to a client
 
 - Default Role
		
- Automatically assign user roles mapping for:
			
- Newly created users
 - Imported users through identity brokering
 
 
 - Automatically assign user roles mapping for:
			
 - Role Mapping: Assign role to individual user or group
 
 - Realm Roles
		
 
Keycloak Deployment
Sizing CPU and memory resources

services:
  keycloak:
    #build: .
    image: quay.io/keycloak/keycloak:25.0.4
    container_name: keycloak-v2504
    command: start
    volumes:
      - ./custom-themes/keywind.jar:/opt/keycloak/providers/keywind.jar
      - ./custom-themes/keycloak-spi-browser-session-api-1.0.jar:/opt/keycloak/providers/keycloak-spi-browser-session-api-1.0.jar
    environment:
      #Database Configuration
      KC_DB: postgres
      KC_DB_URL: 'jdbc:postgresql://insert-ip-here:5432/keycloakdb'
      KC_DB_USERNAME: kc_dba
      KC_DB_PASSWORD: 'dbpasshere'
      #Hostname Configuration
      KC_HOSTNAME: 'kc-training.fotia.com.my'
      KC_HOSTNAME_URL: 'https://kc-training.fotia.com.my'
      KC_PROXY: edge
      KC_HOSTNAME_ADMIN_URL: 'https://kc-training.fotia.com.my'
      #Admin Credentials
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: 'admin'
    ports:
      #- 8443:8443
      - 8480:8080
RUn USING docker compose
FEATURES
1) SSO
- Single Sign On
 - Single Sign Out
 - Maintain a single session across multiple applications
 

2) Identity Brokering & Social Login
- Enable login with Social Network
 - Authenticate user with existing OpenID Connect or SAML 2.0 Identity Providers
 

3) User Federation
- Built-in support to sync users from existing LDAP or Active Directory servers
 - Can also implement your own provider if you have users in other stores such as RDBMS
 

4) Client Adapters
- Adapters available for a number of platforms and programming languages
 - Can also use any OpenID Connect Resource Library or SAML 2.0 Service Provider library
 
5) Admin Console
- Centrally manage all aspects of the keycloak server
 - Enable/Disable various features
 - Configure identity brokering and user federation
 - Create and manage application services and define fine-grained authorization policies
 - Manage users including permissions and sessions
 

6) Account Management Console
- User can manage their own accounts:
	
- Update Profile
 - Change password
 - Setup 2FA
 
 - Manage session
 - View history account
 - Link account with additional provider
 

7) Standard Protocols
- Keycloak is based on standard protocols and provide support for:
	
- OpenID Connect
 - OAuth 2.0
 - SAML
 
 

8) Authorization Services
- Manage permissions for all your services from Keycloak Admin Console
 - Gives you the power to define exactly the policies you need
 
9) Themes Customization
- Cutomize all pages
 - Pages is using .ftl formats
	
- HTML
 - CSS
 - JS
 
 - Limitless customization
 - Organization Branding
 
11) Clustering
- Run Keycloak in the cluster for Scalability and HA
 
10) Password Policies
- Lots of policies supported for a password. ex:
	
- Digits
 - Special Character
 - Expire Password
 - Not username
 - Minimum Length
 - etc..
 
 
OAuth-Open authorization
Open protocol for authorization that allows user to share private resources to 3rd-party
What is oauth?
- Open standard for authorization that apps can use to provide client applications with "secure delegated access"
 - OAuth works over HTTPS and authorizes devices, APIs, servers and applications with access tokens rather than credentials
 - 2 version:
 
why oauth?
- Was created as a response to the direct authentication pattern.
 - Instead of sending username and password to the server with each request, the user sends an API key ID and secret.
 
Oauth actors
- Resource Owner: Owns the data in the resource server
 - Resource Server: The API which stores the data the application wants to access
 - Authorization Server: The main engine of OAuth
 - Client: The application that want to access the data
 


exp = expiry time (number of second from epoch time 1 jan 1970)
iat = issued time of the token
Differences of iat n exp is lifespan of access token
auth_time = time of authorization occur
jti = jwt unique identifier given to this token
iss = the issuer of this token which is keycloak
aud = who is this token for? by default keycloak set it as account
sub = subject of JWT, the user who is actually logged in, its a unique ID assigned/given by keycloak
typ = type of the token, which is Bearer token
azp = authorized party, application/client who was authorized to use the access token
nonce = must match what is sent for validation. * check from previous parameter
ssession_state = sso session ID between the user and the keycloak authorization server
scope = its what we requested, and thats exaclt what we got. It will be mapping directly to the authorities
sid = sso session id
> and a bunch of other claims i.e: email_verified, name, preferred_username, given_name, etc..
> these was show up because we have email and profile in the scope, and thats the information we get in the access token.JWT Access Token
Keycloak Tokens
- Access-Token short-lived (Minutes+): Used for accessing resources
 - Refresh-Token longer-lived (Hours+): Used for requesting new Tokens
 - IDToken: Contains User Information (OIDC)
 - Offline-Token long-lived(Days++)
 
oauth grant types
- Authorization Code Grant Flow
 - Authorization Code with PKCE
 Implicit Grant Flow (Deprecated)- Client Credentials Grant Flow
 Password Grant Flow (Deprecated)
OAuth flow
How Keycloak SSO Works?
Web SSO with OIDC: Unauthenticated User

Web SSO with OIDC: Authenticated User

Keycloak Tokens
- Access-Token short-lived (Minutes+): Used for accessing resources
 - Refresh-Token longer-lived (Hours+): Used for requesting new Tokens
 - IDToken: Contains User Information (OIDC)
 - Offline-Token long-lived(Days++)
 
Calling Backend Services with Access-Token

Sample Plain js
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Keycloak JS Demo</title>
    <style>
        body {
            background-color: #eaeaea;
            font-family: sans-serif;
            font-size: 30px;
        }
        button {
            font-family: sans-serif;
            font-size: 30px;
            width: 250px;
            background-color: #0085cf;
            background-image: linear-gradient(to bottom, #00a8e1 0%, #0085cf 100%);
            background-repeat: repeat-x;
            border: 2px solid #ccc;
            color: #fff;
            text-transform: uppercase;
            -webkit-box-shadow: 2px 2px 10px 0px rgba(0, 0, 0, 0.5);
            -moz-box-shadow: 2px 2px 10px 0px rgba(0, 0, 0, 0.5);
            box-shadow: 2px 2px 10px 0px rgba(0, 0, 0, 0.5);
        }
        button:hover {
            background-color: #006ba6;
            background-image: none;
            -webkit-box-shadow: none;
            -moz-box-shadow: none;
            box-shadow: none;
        }
        hr {
            border: none;
            background-color: #eee;
            height: 10px;
        }
        .menu {
            padding: 10px;
            margin-bottom: 10px;
        }
        .content {
            background-color: #eee;
            border: 1px solid #ccc;
            padding: 10px;
            -webkit-box-shadow: 2px 2px 10px 0px rgba(0, 0, 0, 0.5);
            -moz-box-shadow: 2px 2px 10px 0px rgba(0, 0, 0, 0.5);
            box-shadow: 2px 2px 10px 0px rgba(0, 0, 0, 0.5);
        }
        .content .message {
            padding: 10px;
            background-color: #fff;
            border: 1px solid #ccc;
            font-size: 40px;
        }
        #token-content {
            font-size: 20px;
            padding: 5px;
            white-space: pre;
            text-transform: none;
        }
        .wrapper {
            position: absolute;
            left: 10px;
            top: 10px;
            bottom: 10px;
            right: 10px;
        }
        .error {
            color: #a21e22;
        }
        table {
            width: 100%;
        }
        tr.even {
            background-color: #eee;
        }
        td {
            padding: 5px;
        }
        td.label {
            font-weight: bold;
            width: 250px;
        }
    </style>
</head>
<body style="display: none;">
<div class="wrapper" id="welcome" style="display: none;">
    <div class="menu">
        <button name="loginBtn" onclick="keycloak.login()">Login</button>
    </div>
    <div class="content">
        <div class="message">Please login</div>
    </div>
</div>
<div class="wrapper" id="profile" style="display: none;">
    <div class="menu">
        <button name="profileBtn" onclick="showProfile()">Profile</button>
        <button name="tokenBtn" onclick="showToken()">AccessToken</button>
        <button name="idTokenBtn" onclick="showIdToken()">IDToken</button>
        <button name="accountBtn" onclick="keycloak.accountManagement()">Account</button>
        <button name="changePasswordBtn" onclick="changePassword()">Password</button>
        <button name="reauthBtn" onclick="enforceCurrentAuth()">ReAuth</button>
        <button name="logoutBtn" onclick="keycloak.logout()">Logout</button>
    </div>
    <div class="content">
        <div id="profile-content" class="message">
            <table cellpadding="0" cellspacing="0">
                <tr>
                    <td class="label">First name</td>
                    <td><span id="firstName"></span></td>
                </tr>
                <tr class="even">
                    <td class="label">Last name</td>
                    <td><span id="lastName"></span></td>
                </tr>
                <tr class="even">
                    <td class="label">Email</td>
                    <td><span id="email"></span></td>
                </tr>
            </table>
        </div>
    </div>
</div>
<div class="wrapper" id="token" style="display: none;">
    <div class="menu">
        <button onclick="showProfile()">Profile</button>
        <button onclick="showToken()">AccessToken</button>
        <button onclick="showIdToken()">IDToken</button>
        <button name="accountBtn" onclick="keycloak.accountManagement()">Account</button>
        <button name="changePasswordBtn" onclick="changePassword()">Password</button>
        <button name="reauthBtn" onclick="enforceCurrentAuth()">ReAuth</button>
        <button onclick="keycloak.logout()">Logout</button>
    </div>
    <div class="content">
        <div id="token-content" class="message"></div>
    </div>
</div>
<div class="wrapper" id="idToken" style="display: none;">
    <div class="menu">
        <button onclick="showProfile()">Profile</button>
        <button onclick="showToken()">AccessToken</button>
        <button onclick="showIdToken()">IDToken</button>
        <button name="accountBtn" onclick="keycloak.accountManagement()">Account</button>
        <button name="changePasswordBtn" onclick="changePassword()">Password</button>
        <button name="reauthBtn" onclick="enforceCurrentAuth()">ReAuth</button>
        <button onclick="keycloak.logout()">Logout</button>
    </div>
    <div class="content">
        <div id="token-content" class="message"></div>
    </div>
</div>
<script>
    let keycloakUrl = "https://kc-v2.fotia.com.my"
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = keycloakUrl + "/js/keycloak.js";
    document.getElementsByTagName('head')[0].appendChild(script);
    window.onload = function () {
        window.keycloak = new Keycloak({
            url: keycloakUrl,
            // realm: 'demo',
            // clientId: 'keycloak-js-demo'
            realm: 'training',
            clientId: 'app2-html'
        });
// Workaround for KEYCLOAK-14414
        window.keycloak._login = window.keycloak.login;
        window.keycloak.login = function (options) {
            if (options) {
                //options.scope="openid email name";
                options.scope = "openid";
            }
            return window.keycloak._login.apply(window.keycloak, [options]);
        };
        keycloak.init({
            onLoad: 'login-required',
            checkLoginIframe: true,
            checkLoginIframeInterval: 1,
            pkceMethod: 'S256'
        }).then(function () {
                if (keycloak.authenticated) {
                    showProfile();
                } else {
                    welcome();
                }
                document.body.style.display = 'block';
            }).catch(function (error) {
        console.error("Failed to initialize Keycloak", error);
        welcome();
    });
        keycloak.onAuthLogout = welcome;
    };
    function welcome() {
        show('welcome');
    }
    function getTimeSinceLastAuth() {
        let timeSinceAuthSeconds = Math.floor((Date.now() - (keycloak.tokenParsed.auth_time * 1000)) / 1000);
        return timeSinceAuthSeconds;
    }
    function enforceCurrentAuth() {
        let timeSinceAuthSeconds = getTimeSinceLastAuth();
        console.log("time since auth: " + timeSinceAuthSeconds);
        if (timeSinceAuthSeconds < 10) {
            console.log("auth is still file")
            return;
        } else {
            console.log("trigger reauth")
        }
        keycloak.login({
            loginHint: keycloak.tokenParsed.preferred_username,
            maxAge: 20
        });
    }
    function changePassword() {
        keycloak.login({
            action: "UPDATE_PASSWORD"
        });
    }
    function showProfile() {
        if (keycloak.tokenParsed['given_name']) {
            document.getElementById('firstName').innerHTML = keycloak.tokenParsed['given_name'];
        }
        if (keycloak.tokenParsed['family_name']) {
            document.getElementById('lastName').innerHTML = keycloak.tokenParsed['family_name'];
        }
        if (keycloak.tokenParsed['email']) {
            document.getElementById('email').innerHTML = keycloak.tokenParsed['email'];
        }
        show('profile');
    }
    function showToken() {
        document.getElementById('token-content').innerHTML = JSON.stringify(keycloak.tokenParsed, null, '    ');
        show('token');
    }
    function showIdToken() {
        document.getElementById('token-content').innerHTML = JSON.stringify(keycloak.idTokenParsed, null, '    ');
        show('token');
    }
    function show(id) {
        document.getElementById('welcome').style.display = 'none';
        document.getElementById('profile').style.display = 'none';
        document.getElementById('token').style.display = 'none';
        document.getElementById('idToken').style.display = 'none';
        document.getElementById(id).style.display = 'block';
    }
</script>
</body>
</html>

Sample Plain js (cont...)
Keycloak admin
REST API
Keycloak documentation
Keycloak extensions
Keycloak CUSTOM TEMPLATE
Customize Freemarker Template Language (FTL) with Keywind
What is FTL?
- Java-based template engine that used by keycloak to generate dynamic HTML
 - Maintained by Apache Foundation
 
<html>
<head>
  <title>${title}
</head>
<body>
  <h1>${title}</h1>
  <p>${exampleObject.name} by ${exampleObject.project}</p>
  <ul>
    <#list systems as system>
      <li>${system_index + 1}. ${system.name} from ${system.project}</li>
    </#list>
  </ul>
</body>
</html>package com.keycloak.freemarker.first;
public class ValueExampleObject {
    private String name;
    private String project;
    public ValueExampleObject(String name, String project) {
        this.name = name;
        this.project = project;
    }
    public String getName() {
        return name;
    }
    public String getProject() {
        return project;
    }
}example
helloworld.ftl
veo.java
package com.keycloak.freemarker.first;
import java.io.File;
import java.io.FileWriter;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.Version;
public class MainTest {
    public static void main(String[] args) throws Exception {
        // 1. Configure FreeMarker
        //
        // You should do this ONLY ONCE, when your application starts,
        // then reuse the same Configuration object elsewhere.
        
        Configuration cfg = new Configuration();
        
        // Where do we load the templates from:
        cfg.setClassForTemplateLoading(MainTest.class, "templates");
        
        // Some other recommended settings:
        cfg.setIncompatibleImprovements(new Version(2, 3, 20));
        cfg.setDefaultEncoding("UTF-8");
        cfg.setLocale(Locale.US);
        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        // 2. Proccess template(s)
        //
        // You will do this for several times in typical applications.
        
        // 2.1. Prepare the template input:
        
        Map<String, Object> input = new HashMap<String, Object>();
        
        input.put("title", "FTL example");
        input.put("exampleObject", new ValueExampleObject("Fotia", "DevOps"));
        
        List<ValueExampleObject> systems = new ArrayList<ValueExampleObject>();
        systems.add(new ValueExampleObject("JDN", "SpotMe"));
        systems.add(new ValueExampleObject("JPN", "MyIdentity"));
        systems.add(new ValueExampleObject("JDN", "MyGPKI"));
        systems.add(new ValueExampleObject("MDI", "APIX INSIST"));
        input.put("systems", systems);
        
        // 2.2. Get the template
        Template template = cfg.getTemplate("helloworld.ftl");
            
        // 2.3. Generate the output
        // Write output to the console
        Writer consoleWriter = new OutputStreamWriter(System.out);
        template.process(input, consoleWriter);
        // For the sake of example, also write output into a file:
        Writer fileWriter = new FileWriter(new File("output.html"));
        try {
            template.process(input, fileWriter);
        } finally {
            fileWriter.close();
        }
    }
}MainTest.java
<#macro page>
  <html>
  <head>
    <title>${title}
  </head>
  <body>
    <h1>${title}</h1>
  
    <#-- This processes the enclosed content:  -->
    <#nested>
  </body>
  </html>
</#macro>
<#macro otherExample p1 p2>
  <p>The parameters were: ${p1}, ${p2}</p>
</#macro>Reuse common template fragments
utils.ftl
<#import "lib/utils.ftl" as u> 
<@u.page>
  <p>${exampleObject.name} by ${exampleObject.developer}</p>
  <ul>
    <#list systems as system>
      <li>${system_index + 1}. ${system.name} from ${system.developer}</li>
    </#list>
  </ul>
  
  <#-- Just another example of using a macro: -->
  <@u.otherExample p1=11 p2=22 />
</@u.page>helloworld.ftl
<#assign var_link = "https://keycloak.org">
<a href="${var_link}">About Keycloak</a>Variables
<#assign name = "${article.getName()}">
<#if name?starts_with("https")>
    <a href="https://keycloak.org</a>
    <#else>
    <a href="https://www.keycloak.org/${name}/25.0.4/rest-api/index.html">${title}</a>
</#if>if then else
- CSS framework which uses utility classes to style HTML code on the fly
 - Fast and more flexible compared to other framework such as Bootstrap and Material
 
- JS Library to implement interactivity to the page without the need to write any custom Javascript
 
custom theme (keywind)
A template for Keycloak built by Tailwind and AlpineJS
THANK YOU
Keycloak
By Abdullah Fathi
Keycloak
- 392