Create a public OpenID Connect client with client id demo-client
.
- Standard flow enabled
- Root URL: e.g. http://localhost:5555
- Valid Redirect URIs: /*
- Web Origins: +
Create a public OpenID Connect client with client id demo-client
.
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Keycloak SPA Demo</title> | |
<style> | |
body { | |
background-color: #eaeaea; | |
font-family: sans-serif; | |
font-size: 10px; | |
} | |
button { | |
font-family: sans-serif; | |
font-size: 25px; | |
width: 200px; | |
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 { | |
font-size: 20px; | |
background-color: #eee; | |
border: 1px solid #ccc; | |
padding: 10px; | |
-webkit-box-shadow: 2px 2px 10px 0 rgba(0, 0, 0, 0.5); | |
-moz-box-shadow: 2px 2px 10px 0 rgba(0, 0, 0, 0.5); | |
box-shadow: 2px 2px 10px 0 rgba(0, 0, 0, 0.5); | |
} | |
.message-content { | |
font-size: 20px; | |
padding: 10px; | |
background-color: #fff; | |
border: 1px solid #ccc; | |
} | |
.token-content { | |
font-size: 20px; | |
padding: 5px; | |
white-space: pre; | |
text-transform: none; | |
} | |
.wrapper { | |
position: absolute; | |
left: 10px; | |
top: 40px; | |
bottom: 10px; | |
right: 10px; | |
} | |
.error { | |
color: #a21e22; | |
} | |
table { | |
width: 100%; | |
} | |
table.credentials, | |
table.profile, | |
table.apps { | |
width: unset; | |
} | |
tr.even { | |
background-color: #eee; | |
} | |
td { | |
padding: 5px; | |
} | |
td.label { | |
font-weight: bold; | |
width: 250px; | |
} | |
.hidden { | |
display: none; | |
} | |
</style> | |
</head> | |
<body> | |
<div> | |
<h1>ClientId: <span id="clientInfo"></span></h1> | |
</div> | |
<div id="welcome" class="wrapper hidden"> | |
<div class="menu"> | |
<button name="loginBtn" onclick="keycloak.login()">Login</button> | |
</div> | |
<div class="message-content"> | |
<div class="message">Please login</div> | |
</div> | |
</div> | |
<div id="content" class="wrapper hidden"> | |
<div class="menu"> | |
<button name="rarBtn" onclick="showRar()" class="token">RAR</button> | |
<button name="tokenBtn" onclick="showToken()" class="token">AccessToken</button> | |
<button name="idTokenBtn" onclick="showIdToken()" class="idToken">IDToken</button> | |
<button name="userinfoBtn" onclick="showUserInfo()" class="userinfo">Userinfo</button> | |
<button name="profileBtn" onclick="showProfile()" class="profile">Profile</button> | |
<button name="reauthBtn" onclick="enforceCurrentAuth()" class="reauth">ReAuth</button> | |
<button name="changePasswordBtn" onclick="changePassword()" class="password">Password</button> | |
<button name="accountBtn" onclick="keycloak.accountManagement()" class="account">Account</button> | |
<button name="logoutBtn" onclick="keycloak.logout()" class="logout">Logout</button> | |
</div> | |
<div id="data" class="content"></div> | |
</div> | |
<script defer> | |
let searchParams = new URLSearchParams(window.location.search); | |
let keycloakBaseUrl = searchParams.get("base_url") || "http://localhost:8081"; | |
let keycloakUrl = keycloakBaseUrl + (searchParams.get("path") || "/auth"); | |
let realm = searchParams.get("realm") || 'demo'; | |
let clientId = searchParams.get("client_id") || 'demo-client'; | |
let scope = searchParams.get("scope") || 'openid email'; | |
// &show=profile,settings,apps,security,logout | |
const allContextClasses = ["profile", "token", "idToken", "userinfo", "logout", "apps", "password", "reauth", "account"]; | |
const contextClassesToHideDefault = ["apps", "password", "reauth", "account", "profile"]; | |
const contextClassesToShowDefault = [...allContextClasses].filter((value, index, arr) => { | |
return !contextClassesToHideDefault.includes(value); | |
}); | |
let contextClassesToShow = searchParams.get("show")?.split(",") || contextClassesToShowDefault; | |
for (let className of allContextClasses) { | |
if (!contextClassesToShow.includes(className)) { | |
let btn = document.querySelector(`button.${className}`); | |
if (btn) { | |
btn.parentElement.removeChild(btn); | |
} | |
} | |
} | |
document.getElementById("clientInfo").textContent = clientId; | |
// dynamically add keycloak.js script | |
let script = document.createElement('script'); | |
script.type = 'text/javascript'; | |
script.src = keycloakUrl + "/js/keycloak.js"; | |
document.getElementsByTagName('head')[0].appendChild(script); | |
window.onload = () => { | |
window.keycloak = new Keycloak({ | |
url: keycloakUrl, | |
realm: realm, | |
clientId: clientId | |
}); | |
let origLoginUrl = window.keycloak.createLoginUrl; | |
window.keycloak.createLoginUrl = function (options) { | |
let url = origLoginUrl.call(window.keycloak, options); | |
// add RAR authorization_details if present | |
if (options && options["authorizationDetails"]) { | |
url += "&authorization_details=" + options["authorizationDetails"]; | |
} | |
return url; | |
}; | |
let initConfig = { | |
onLoad: 'login-required', // redirects to login if not login | |
// onLoad: 'check-sso', // shows login button of not logged in | |
checkLoginIframe: true, | |
checkLoginIframeInterval: 1, | |
pkceMethod: 'S256', | |
scope: scope | |
}; | |
let onLoginSuccess = () => { | |
if (keycloak.authenticated) { | |
showProfile(); | |
} else { | |
showWelcome(); | |
} | |
}; | |
keycloak.init(initConfig).success(onLoginSuccess); | |
keycloak.onAuthLogout = showWelcome; | |
}; | |
function showRar() { | |
let data = [ | |
{ | |
"type": "payment_initiation", | |
"actions": [ | |
"initiate", | |
"status", | |
"cancel" | |
], | |
"locations": [ | |
"https://example.com/payments" | |
], | |
"instructedAmount": { | |
"currency": "EUR", | |
"amount": "123.50" | |
}, | |
"creditorName": "Merchant123", | |
"creditorAccount": { | |
"iban": "DE02100100109307118603" | |
}, | |
"remittanceInformationUnstructured": "Ref Number Merchant" | |
} | |
]; | |
let jsonString = JSON.stringify(data, null, ' ').trim(); | |
let rarHtml = ` | |
<button onclick="sendRar()">Authorize</button> | |
<div id="rarData" contenteditable class="token-content">${jsonString}</div> | |
`; | |
show(rarHtml, "token-content"); | |
} | |
function sendRar() { | |
let rarDataJson = document.getElementById("rarData").textContent; | |
let encodedRarData = encodeURIComponent(rarDataJson); | |
keycloak.login({ | |
authorizationDetails: encodedRarData | |
}); | |
// kc.loginWithAuthorizationDetails(); | |
} | |
function showWelcome() { | |
document.getElementById("welcome").classList.remove("hidden"); | |
document.getElementById("content").classList.add("hidden"); | |
} | |
function getTimeSinceLastAuth() { | |
let timeSinceAuthInSeconds = Math.floor((Date.now() - (keycloak.tokenParsed.auth_time * 1000)) / 1000); | |
return timeSinceAuthInSeconds; | |
} | |
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: 12 | |
}); | |
} | |
function formatDate(timestamp) { | |
return new Intl.DateTimeFormat('de-DE', { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(timestamp)) | |
} | |
function changePassword() { | |
keycloak.login({ | |
action: "UPDATE_PASSWORD" | |
}); | |
} | |
function showProfile() { | |
let profileHtml = ` | |
<table class="profile"> | |
<tr> | |
<td class="label">First name</td> | |
<td><span id="firstName">${keycloak.tokenParsed['given_name']}</span></td> | |
<td></td> | |
</tr> | |
<tr> | |
<td class="label">Last name</td> | |
<td><span id="lastName">${keycloak.tokenParsed['family_name']}</span></td> | |
<td></td> | |
</tr> | |
<tr> | |
<td class="label">Email</td> | |
<td><span id="email">${keycloak.tokenParsed['email']}</span></td> | |
<td></td> | |
</tr> | |
</table> | |
`; | |
show(profileHtml, "message-content"); | |
} | |
function showToken() { | |
let data = JSON.stringify(keycloak.tokenParsed, null, ' '); | |
show(data, "token-content"); | |
} | |
function showIdToken() { | |
let data = JSON.stringify(keycloak.idTokenParsed, null, ' '); | |
show(data, "token-content"); | |
} | |
async function showUserInfo() { | |
await keycloak.updateToken(5); | |
let userInfoData = await keycloak.loadUserInfo(); | |
let data = JSON.stringify(userInfoData, null, ' '); | |
show(data, "token-content"); | |
} | |
function show(data, cssClass) { | |
let contentElement = document.getElementById('content'); | |
contentElement.classList.remove("hidden") | |
let dataElement = document.getElementById('data'); | |
dataElement.innerHTML = data; | |
dataElement.classList.remove(["message-content", "token-content"]); | |
dataElement.classList.add(cssClass); | |
} | |
</script> | |
</body> | |
</html> |
Linking my account TrustwalltGuide on Twitter with my address 0x40d2c12caacffc547e9cf2c60c02c9ec0b28eae5 on EVM in mycryptoprofile.io, and the challenge code is: 9cc97dfa0b56f9c0fa1a362c24a17b4b. #LitentryVerifyMyAddress