Abdullah Fathi
When?
When?
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
Open protocol for authorization that allows user to share private resources to 3rd-party
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.<!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>
Customize Freemarker Template Language (FTL) with Keywind
<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;
    }
}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>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><#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>A template for Keycloak built by Tailwind and AlpineJS