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