Last active
February 3, 2025 13:54
-
-
Save sscarduzio/798a806ade4b86a7d2d56b1b1aa6a047 to your computer and use it in GitHub Desktop.
Zero bullshit Keycloak authentication for Express JS - from
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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)) | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
} | |
] | |
} | |
] | |
} | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
Usage
For login, open browser at
http://localhost:3000/login
and login with credentials "user:password", and observe the user info. I.e.For logout, open
http://localhost:3000/logout
. Now you if you go to /login you will see credentials are requested again by KC.