Skip to content

Instantly share code, notes, and snippets.

@sscarduzio
Last active April 15, 2024 15:45
Show Gist options
  • Save sscarduzio/798a806ade4b86a7d2d56b1b1aa6a047 to your computer and use it in GitHub Desktop.
Save sscarduzio/798a806ade4b86a7d2d56b1b1aa6a047 to your computer and use it in GitHub Desktop.
Zero bullshit Keycloak authentication for Express JS - from
/*
SEE FIRST COMMENT FOR DESCRIPTION AND USAGE
*/
const Keycloak = require('keycloak-connect')
const express = require('express')
const session = require('express-session')
const app = express()
const LOGIN_PATH = '/login'
const LOGOUT_PATH = '/logout'
const SESSION_STORE_PASS = '123456789012345678901234567890!!!'
const server = app.listen(3000, function () {
const host = server.address().address
const port = server.address().port
console.log('Example app listening at http://%s:%s', host, port)
})
app.get('/', function (req, res) {
res.redirect(LOGIN_PATH)
})
// Create a session-store to be used by both the express-session
// middleware and the keycloak middleware.
const memoryStore = new session.MemoryStore()
app.use(session({
secret: SESSION_STORE_PASS,
resave: false,
saveUninitialized: true,
store: memoryStore
}))
const kcOptions = {
"realm": "nodejs-example",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required": "external",
"resource": "nodejs-connect",
"public-client": true
}
console.log("Starting Keycloak connector with options: ", kcOptions)
const keycloak = new Keycloak({store: memoryStore}, kcOptions)
app.use(keycloak.middleware({
logout: LOGOUT_PATH, // <-- this is supposed to delete the session and log you out from KC as well
admin: '/'
}))
app.get('/login', keycloak.protect(), async function (req, res) {
let token
try {
const grant = await keycloak.getGrant(req, res)
token = grant.access_token
console.log(`Found token ${token}`)
} catch (e) {
console.log("Unable to find the token in KC response ", req.session)
throw new Error(e)
}
const userProfile = await keycloak.grantManager.userInfo(token)
console.log("Found user profile:", userProfile)
res.header("Content-Type", 'application/json')
return res.send(JSON.stringify(userProfile, null, 4))
})
{
"realm": "nodejs-example",
"enabled": true,
"sslRequired": "external",
"registrationAllowed": true,
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"requiredCredentials": [ "password" ],
"users" : [
{
"username" : "user",
"enabled": true,
"email" : "sample-user@nodejs-example",
"firstName": "Sample",
"lastName": "User",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": [ "user" ],
"clientRoles": {
"account": ["view-profile", "manage-account"]
}
}
],
"roles" : {
"realm" : [
{
"name": "user",
"description": "User privileges"
},
{
"name": "admin",
"description": "Administrator privileges"
}
]
},
"scopeMappings": [
{
"client": "nodejs-connect",
"roles": ["user"]
}
],
"clients": [
{
"clientId": "nodejs-connect",
"enabled": true,
"publicClient": true,
"baseUrl": "/",
"adminUrl" : "http://localhost:3000/",
"baseUrl" : "http://localhost:3000/",
"redirectUris": [
"http://localhost:3000/*"
],
"webOrigins": []
},
{
"clientId": "nodejs-apiserver",
"enabled": true,
"secret": "secret",
"redirectUris": [
"http://localhost:3000/*"
],
"webOrigins": [
"http://localhost:3000/*"
],
"serviceAccountsEnabled": true,
"authorizationServicesEnabled": true,
"authorizationSettings": {
"resources": [
{
"name": "resource",
"type": "urn:nodejs-apiserver:resources:default",
"ownerManagedAccess": false,
"uris": [
"/*"
],
"scopes": [
{
"name": "view"
},
{
"name": "write"
}
]
}
]
}
}
]
}
@sscarduzio
Copy link
Author

Motivation

For some reason I had difficulties to find good examples for Javascript. There were a lot of assumptions (i.e. that the base usecase involves crazy role based access control over certain paths... When 80% of people just want to get authorized and user profile information).

Keycloak

Install Keycloak, the docker one-liner is OK.

Then import the pre configured example realm. Added here in case it changes, or they remove it.

Express server

npm i --save express express-session keycloak-connect

node index.js

Usage

For login, open browser at http://localhost:3000/login and login with credentials "user:password", and observe the user info. I.e.

{
    "sub": "6a13186f-fb0b-4073-8b3a-4279878eb320",
    "email_verified": false,
    "name": "Sample User",
    "preferred_username": "user",
    "given_name": "Sample",
    "family_name": "User",
    "email": "sample-user@nodejs-example"
}

For logout, open http://localhost:3000/logout. Now you if you go to /login you will see credentials are requested again by KC.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment