Lab - Simple OIDC Provider

Context Help understanding how an ODIC provider works. Be able to set up and experiment with an OIDC client to interact with .

Preparation

Create a folder and initialize a Node.js project

$ mkdir oidcsrv 
$ cd oidcsrv
$ yarn init -y
$ yarn add express oidc-provider

Change the project to be ESM by adding or updating the line in package.json:

{
  ...
  "type": "module",
  ...
}

Add the Code

Add index.js in the top folder:

import express from 'express';
import Provider from 'oidc-provider';

const app = express();

const configuration = {
  clients: [{
    client_id: 'some_client_id',
    client_secret: 'some_client_secret',
    grant_types: ['authorization_code'],
    redirect_uris: ['http://localhost:8080/login/callback'],
    post_logout_redirect_uris: ['http://localhost:8080/'],
    response_types: ['code'],
  }],
  pkce: {
    required: () => true,
  },
  async findAccount(ctx, id) {
    return {
      accountId: id,
      async claims(use, scope) {
        return { sub: id };
      },
    };
  },
};
const oidc = new Provider('http://localhost:3000', configuration);

app.use('/oidc', oidc.callback());

app.listen(3000, function () {
  console.log('OIDC is listening on http://localhost:3000');
});

Launch the Server

Launch the server with node index.js:

$ node index.js

# Warning skipped
OIDC is listening on http://localhost:3000

Notes

  • The server is set up with one client of specific configuration
  • PKCE is made required, which is also true by default in most cases
  • Any user is accepted, with template reply
  • The provider is at the location /oidc, so the client need to set issuer correctly

email and profile Scopes and Claims

In order to serve additional scopes other than openid, such as the common email and profile scopes, the code index.js can be changed to:

import express from 'express';
import Provider from 'oidc-provider';

const app = express();

const configuration = {
  clients: [{
     client_id: 'oidccli_id',
     client_secret: 'oidccli_secret',
     grant_types: ['authorization_code'],
     redirect_uris: ['http://localhost:8080/login/callback'],
     post_logout_redirect_uris: ['http://localhost:8080/'],
     response_types: ['code'],
  }],
  scopes: [
    'email', 'profile',
  ],
  claims: {
    email: ['email'],
    profile: ['first_name'],
  },
  async findAccount(ctx, id) {
    return {
      accountId: id,
      async claims(use, scope) {
        console.log("USE", use, "SCOPE", scope);
        return { sub: id, email: `${id}@example.com`, first_name: id };
      },
    };
  },
};
const oidc = new Provider('http://localhost:3000', configuration);

app.use('/oidc', oidc.callback());

app.listen(3000, function () {
  console.log('OIDC is listening on http://localhost:3000');
});
  • Here the findAccount() function needs to be revised, of course.
  • The scopes and claims settings configure what scopes, and which attributes for each scope, to provide