Keycloak Express Openid-client

austincunningham

Austin Cunningham

Posted on February 25, 2022

Keycloak Express Openid-client

Keycloak is deprecating their client adapters (keycloak-connect) for Node and recommending openid-client as a replacement.

Setup Keycloak

First I download keycloak extract it and you can run it with the following command



bin/kc.sh start-dev


Enter fullscreen mode Exit fullscreen mode

You can then login http://localhost:8080, first time you do keycloak asks you to set an admin user and password.

Create a Realm and give it an name and create it. I am using keycloak-express for my realm name
Create realm

The create a Client using openid-connect in the Realm
Create a client

Set the Valid Redirect URIs and select save,
set valid redirect URIs

NOTE:you can specify specific routes here but I am using a wild card(not recommend best practice)

Create a user its documented here so I won't go into it.

That's it for Keycloak setup

Setup Openid-client with Passport in Express

We are going to use this openid-client and passport to connect to keycloak. I install the following



npm install passport
npm install openid-client
npm install express-session
npm install express


Enter fullscreen mode Exit fullscreen mode

From the Realm we need the openid-configuration can be got from an endpoint



/realms/{realm-name}/.well-known/openid-configuration


Enter fullscreen mode Exit fullscreen mode

So in my case the realm name is keycloak-express so the url will be http://localhost:8080/realms/keycloak-express/.well-known/openid-configuration the output is as follows
.well-known url output
All we need is this issuer:"http://localhost:8080/realms/keycloak-express" url to connect openid-client to keycloak as follows



'use strict';

import express from 'express';
import { Issuer, Strategy } from 'openid-client';
import passport from 'passport';
import expressSession from 'express-session';

const app = express();

// use the issuer url here
const keycloakIssuer = await Issuer.discover('http://localhost:8080/realms/keycloak-express')
// don't think I should be console.logging this but its only a demo app
// nothing bad ever happens from following the docs :)
console.log('Discovered issuer %s %O', keycloakIssuer.issuer, keycloakIssuer.metadata);

// client_id and client_secret can be what ever you want
// may be worth setting them up as env vars 
const client = new keycloakIssuer.Client({
    client_id: 'keycloak-express',
    client_secret: 'long_secret-here',
    redirect_uris: ['http://localhost:3000/auth/callback'],
    post_logout_redirect_uris: ['http://localhost:3000/logout/callback'],
    response_types: ['code'],
  });


Enter fullscreen mode Exit fullscreen mode

I then setup express sessions



var memoryStore = new expressSession.MemoryStore();
app.use(
    expressSession({
    secret: 'another_long_secret',
    resave: false,
    saveUninitialized: true,
    store: memoryStore
    })
);


Enter fullscreen mode Exit fullscreen mode

Then setup passport to use open connect id strategy



app.use(passport.initialize());
app.use(passport.authenticate('session'));

// this creates the strategy
passport.use('oidc', new Strategy({client}, (tokenSet, userinfo, done)=>{
        return done(null, tokenSet.claims());
    })
)

passport.serializeUser(function(user, done) {
    done(null, user);
  });
passport.deserializeUser(function(user, done) {
    done(null, user);
});


Enter fullscreen mode Exit fullscreen mode

Most of above is copied from the passport docs, I found this blog helpful in explaining serialize/deserialize.

Next I setup the authentication route this makes use of the the callback redirect_uris: from the keycloakIssuer.Client



// default protected route /test
app.get('/test', (req, res, next) => {
    passport.authenticate('oidc')(req, res, next);
});

// callback always routes to test 
app.get('/auth/callback', (req, res, next) => {
    passport.authenticate('oidc', {
      successRedirect: '/testauth',
      failureRedirect: '/'
    })(req, res, next);
});


Enter fullscreen mode Exit fullscreen mode

I then setup a function to check if a route is authenticated



// function to check weather user is authenticated, req.isAuthenticated is populated by password.js
// use this function to protect all routes
var checkAuthenticated = (req, res, next) => {
    if (req.isAuthenticated()) { 
        return next() 
    }
    res.redirect("/test")
}


Enter fullscreen mode Exit fullscreen mode

This can then be used on protected routes



app.get('/testauth', checkAuthenticated, (req, res) => {
    res.render('test');
});

app.get('/other', checkAuthenticated, (req, res) => {
    res.render('other');
});

//unprotected route
app.get('/',function(req,res){
    res.render('index');
});


Enter fullscreen mode Exit fullscreen mode

Finally I set the logout route up this also uses a callback post_logout_redirect_uris from the keycloakIssuer.Client



// start logout request
app.get('/logout', (req, res) => {
    res.redirect(client.endSessionUrl());
});

// logout callback
app.get('/logout/callback', (req, res) => {
    // clears the persisted user from the local storage
    req.logout();
    // redirects the user to a public route
    res.redirect('/');
});


Enter fullscreen mode Exit fullscreen mode

And set the app to listen



app.listen(3000, function () {
  console.log('Listening at http://localhost:3000');
});


Enter fullscreen mode Exit fullscreen mode

Repo here with some extra code around views. Looks like this

login flow

💖 💪 🙅 🚩
austincunningham
Austin Cunningham

Posted on February 25, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Keycloak Express Openid-client
keycloak Keycloak Express Openid-client

February 25, 2022