Lab - Simple OIDC Client

Context After seeing how OIDC client works using Passport.js (see lab), try again without.

Preparation

Create a folder and initialize a Node.js project

$ mkdir oidcapp
$ cd oidcapp
$ yarn init -y
$ yarn add express express-session openid-client

Add the Code

Add simple.js in the top folder:

const express = require('express');
const session = require('express-session');

const { Issuer, generators } = require('openid-client');

const app = express();

app.use(session({ secret: 'secret', resave: false, saveUninitialized: true }));

var oidcClient;

Issuer.discover('http://localhost:3000/oidc')
.then(issuer => {
    oidcClient = new issuer.Client({
      client_id: 'oidccli_id',
      client_secret: 'oidccli_secret',
      redirect_uris: ['http://localhost:8080/login/callback'],
      post_logout_redirect_uris: ['http://localhost:8080/'],
      response_types: ['code'],
    });
});

app.get('/login', (req, res, next) => {
    if (req.session.user) return res.redirect('/profile');

    const code_verifier = generators.codeVerifier();
    const code_challenge = generators.codeChallenge(code_verifier);

    req.session.code_verifier = code_verifier;

    const url = oidcClient.authorizationUrl({
        scope: 'openid',
        code_challenge,
        code_challenge_method: 'S256',
    });
    res.redirect(url);
});

app.get('/login/callback', async (req, res, next) => {
    const code_verifier = req.session.code_verifier;

    const params = oidcClient.callbackParams(req);
    const tokenSet = await oidcClient.callback(
       'http://localhost:8080/login/callback', params, { code_verifier },
    );
    delete req.session.code_verifier;

    const claims = tokenSet.claims();
    console.log('validated ID Token claims %j', claims);
    req.session.user = {
        ...claims,
        name: claims.sub,
        email: claims.sub + '@example.com',
        id_token: tokenSet.id_token,
    };

    return res.redirect('/profile');
});

app.get('/', (req, res) => {
    if (req.session.user) {
        res.send(`Hi, ${req.session.user.name} (${req.session.user.email}),<br>
            you can go to <a href="/profile">profile</a>, or<br>
            <a href="/logout">logout locally</a>, or<br>
            <a href="/logout?oidc=1">logout globally</a>`);
    } else {
        res.send('<a href="/login">Log in with OIDC Provider</a>')
    }
});
app.get('/profile', (req, res) =>{
    res.send(`<pre>${JSON.stringify(req.session.user, null, 2)}</pre><a href="/">home</a>`);
});
app.get('/logout', (req, res) =>{
    if (!req.session.user) return res.redirect('/');

    let { token_id } = req.session.user;
    delete req.session.user;

    if (!req.query.oidc) {
        res.redirect('/');
    } else {
        const url = oidcClient.endSessionUrl({ token_id_hint: token_id });
        res.redirect(url);
    }
});

app.listen(8080, () => {
    console.log('Server running at http://localhost:8080');
});

Launch the Client App

Launch the client with node simple.js:

$ node simple.js
Server running at http://localhost:8080

Notes