npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@digipolis/auth

v2.4.2

Published

This package contains two components:

Downloads

88

Readme

@digipolis/auth

This package contains two components:

  1. A router which exposes a couple of endpoints which can be used to implement login in your application.
  2. A middleware that can be used to enable single sign-on (SSO) between different apps inside the antwerp/digipolis ecosystem.

In this version aprofiel with assurance levels and different authentication methods is supported (for mprofiel support, check out version 1.X.X).

Setup

You should use express-session in your application to enable session storage.

Be sure to load this middleware before other routes in your application, this enables the automatic refresh of the user's access token.

trust proxy should also be set to true to enable automatic generation of the application's login callback.

Basic example

import {
  createRouter, 
  createSSOMiddleware
} from '@digipolis/auth';
import Express from 'express';
import Session from 'express-session';

const app = new Express();
app.use(Session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: true }
}));

app.enable('trust proxy');


const authConfig = {
  clientId: 'client-id',
  clientSecret: 'client-secret',
  oauthHost: 'https://api-oauth2-a.antwerpen.be',
  basePath: '/auth',
  defaultScopes: [
    'astad.aprofiel.v1.name',
    'astad.aprofiel.v1.avatar',
    'astad.aprofiel.v1.email',
  ],
  scopeGroups: {
    address: ['crspersoon.housenumber', 'crspersoon.streetname'],
    personal: ['crspersoon.nationalnumber', 'crspersoon.nationality']
  },
  url: 'https://api-gw-a.antwerpen.be/acpaas/shared-identity-data/v1' ,
  consentUrl: 'https://api-gw-a.antwerpen.be/acpaas/consent/v1',
};


// This exposes endpoints to enable login 
app.use(createRouter(app, authConfig));

// This middleware enables SSO
const sso = createSSOMiddleware(authConfig);

// The sso middleware should be used where you serve your application
// it relies on redirects, which cannot be used with ajax calls.
router
.get('/index', sso, (req, res) => res.send('hello world'));

Configuration

The login router & the SSO middleware use the same configuration.

  • logLevel: defaults to error
  • basePath: string (default: '/auth')
    Each of the routes in the auth router will be prepended with this property
  • clientId: string
    Client credentials from the API gateway (see section api store)
  • clienSecret: string
    Client credentials from the API gateway (see section api store)
  • consentUrl: string
    The url of the consent api is necessary to enable SSO (also see section api store).
    (e.g. https://api-gw-a.antwerpen.be/acpaas/consent/v1)
  • shouldUpgradeAssuranceLevel: boolean default true Whether you should upgrade to an higher assurancelevel when you already have a session in the app defaults to true (can be disabled for performance reasons)
  • defaultScopes: string[ ]
    list of scopes you will always use (see section scopes) Should be compatible with assurance level = low
  • errorRedirect: string (default: '/')
    Where your application should redirect when something goes wrong during the login process.
  • hooks: object Hooks can be used to add custom logic to the login process. Can be used to modify your session or clean up when logging out.
    Each hook has the same signature as express middleware
    (req, res, next) => {})
    • preLogin: function[ ]
      List of functions that will be executed before login
    • preLogout: function[ ]
      List of functions that will be executed before logout
    • loginSuccess: function[ ]
      List of functions that will be executed when login has succeeded
    • logoutSuccess: function[ ]
      List of functions that will be executed when logout has succeeded
  • key: string (default: 'user')
    The loggedin user will be stored on req.session. This property defines where the user and his accesstoken will be stored.
    For 'user', the user will be at req.session.user and the accesstoken will be at req.session.userToken
  • logout (optional, but needed for single logout(SLO) with the event handler)
    • headerKey string
      the name of the http header where the key is located (defaults to x-logout-token)
    • securityHash string
      bcrypt hash of the token that will be placed in the http header.
    • sessionStoreLogoutAdapter Function
      function that returns a promise when the sessionStore has been successfully updated and rejects otherwise. This adapter is responsible for removing the session. More information
  • oauthHost: string This is where the actual login process starts after leaving your application. This is needed to generate a redirect url to the login page
    (e.g.: https://api-oauth2-a.antwerpen.be)
  • refresh: boolean (default false)
    Enables automatic refresh of the user's access token
  • scopeGroups: object
    scopeGroups is an object where all keys should have an array of scopes as values. These can be used to request additional scopes when logging in through the use of the query parameter scopeGroups (available scopes)
  • url: String Url where the user will be retrieved with after login has succeeded
    (e.g.: https://api-gw-a.antwerpen.be/acpaas/shared-identity-data/v1)
  • allowedDomains: String[] List of domains allowed to redirect to after successful login
    (e.g.: ['antwerpen.be']) (e.g.: ['antwerpen.be', 'digipolis.be', 'gate15.be'])

API Store configuration

For this module to fully work, some configuration on the API store is required.

After creating your application on the api store, you should create a contract with the Shared Identity Data API.

Create Contract shared identity

and the Consent API (if you want to enable SSO) Create Contract consent

The next step is to navigate to your applications in the publisher and clicking on Instellingen > Client Keys > <environment>

You'll find your clientId and secret here.

configure callback

You'll need to configure your callback path here normally, it will be <protocol>://<your-domain>/auth/login/callback (this module exposes this endpoint) (change the basePath if you have configured another)

Eventhandler configuration (for SLO)

Navigate to the eventhandler and go to the oauth namespace oauth namespace

Add a new wildcard subscription

subscription configuration the push url is <protocol>://<hostname>{basePath}/event/loggedout/ (basePath defaults to auth).

You should add a custom header which corresponds to the headerKey in your logout configuration (defaults to x-logout-token). Add your token. (this token will not be known to your application, only the hashed version) (don't forget to click the plus symbol)

How to use the Routes

GET {basePath}/login

This endpoint can be used to login. There are some query parameters available to control in which ways the user can login and which scopes the user can use.

Query parameters

  • scopeGroups
    comma seperated list of the keys of the scopeGroups configured in your configuration. If none are given, only the default scopes from the configration are requested.

  • minimal_assurance_level (default: low for context citizen, substantial for context enterprise)
    possible values: low, substantial, high
    Determines which authentication methods are available to the user.
    If specified, only authentication methods corresponding with the specified assurance level will be available for the user to log in with. See Available authentication methods for info about which authentication methods correspond to which assurance levels.

  • fromUrl (default /)
    Where the user should be redirected if the login process is successful.

  • context (enterprise, citizen or enterprise-citizen) (default citizen)
    Specifies whether the user should log in as a citizen or as an enterprise user. Logging in with context enterprise enables the application to fetch additional enterprise related roles from the authz api with the access token of the user. The context enterprise-citizen presents the user with a choice if they want to login with either citizen or their enterprise.

  • save_consent (default: true) optional query parameter that can be used if you do not want to save the consent.

  • force_auth (default: false) optional query parameter that can be used to force the login screen to show, even if a user is loggedin on the identity provider.

  • auth_methods
    A comma separated list of the auth methods to allow the user to log in with. This limits the list of authentication methods provided to the user by the minimal_assurance_level parameter (if specified) and the context.

    Note that you cannot provide conflicting auth methods with those determined by either the minimal_assurance_level parameter or the context parameter.

    e.g.:

    • auth_methods=iam-aprofiel-userpass&context=enterprise
      (enterprise context requires a minimal assurance level of substantial, iam-aprofiel-userpass has an assurance level of low)

    • auth_methods=iam-aprofiel-userpass&minimal_assurance_level=high
      (iam-aprofiel-userpass has an assurance level of low, which is not sufficient for the specified minimal assurance level)

    See Available authentication methods for a comprehensive list of available authentication methods.

GET {basePath}/isloggedin

The isloggedin endpoint can be used to check if the user is currently loggedIn

{
  isLoggedin: true,
  user: { ... } // this corresponds to the key that is configured in the serviceProvider
}

If the user is not logged in the following payload is returned.

{
  isLoggedin: false
}

GET {basePath}/logout

Redirects the user to the logout. This will cause the session to be destroyed on the IDP. The fromUrl query parameter can be used to redirect the user to a given page after logout.

Available scopes

| Scope | Alias | Minimal assurance level | | ------------------------------- | ----------------- | ----------------------- | | astad.aprofiel.v1.address | aprofiel.address | low | | astad.aprofiel.v1.all | aprofiel.all | low | | astad.aprofiel.v1.avatar | aprofiel.avatar | low | | astad.aprofiel.v1.email | aprofiel.email | low | | astad.aprofiel.v1.name | aprofiel.name | low | | astad.aprofiel.v1.phone | aprofiel.phone | low | | astad.aprofiel.v1.username | aprofiel.username | low | | crspersoon.birthdate | | substantial | | crspersoon.death | | substantial | | crspersoon.deathdate | | substantial | | crspersoon.familyname | | substantial | | crspersoon.gendercode | | substantial | | crspersoon.givenName | | substantial | | crspersoon.housenumber | | substantial | | crspersoon.housenumberextension | | substantial | | crspersoon.municipalityname | | substantial | | crspersoon.municipalityniscode | | substantial | | crspersoon.nationality | | substantial | | crspersoon.nationalnumber | | substantial | | crspersoon.postalcode | | substantial | | crspersoon.registrationstate | | substantial | | crspersoon.streetname | | substantial |

Available authentication methods

| Name | Assurance level | Context | Description | | --------------------- | --------------- | -----------|--------------------------------------------------------------- | | iam-aprofiel-userpass | low | citizen | Our default aprofiel authentication with username and password | | iam-aprofiel-phone | low | citizen | Our aprofiel authentication with phone and code | | fas-citizen-bmid | substantial | citizen | Belgian Mobile ID (e.g. Itsme) | | fas-citizen-otp | substantial | citizen | Authentication with one time password (e.g. sms) | | fas-citizen-totp | substantial | citizen | Time-based one time password (e.g. Google Authenticator) | | fas-citizen-eid | high | citizen | Authentication with eID-card and pin-code | | fas-enterprise-bmid | substantial | enterprise | Belgian Mobile ID (e.g. Itsme) | | fas-enterprise-otp | substantial | enterprise | Authentication with one time password (e.g. sms) | | fas-enterprise-totp | substantial | enterprise | Time-based one time password (e.g. Google Authenticator) | | fas-enterprise-eid | high | enterprise | Authentication with eID-card and pin-code |

Creating and using SessionStoreLogoutAdapters

Your sessionstore can be backed by your server's memory or a database system like postgreSQL, mongodb. Because we have no generic way to query each type of store, we introduce the concept of adapters.

function adapter(sessionKey: String, accessTokenKey: String, userInformation: Object): Promise

An adapter should return a promise which resolves if it succeeds in altering the session or rejects when it fails.

It has 3 arguments:

  • sessionKey: This is the key under which your user is stored in the session (this is the same as the key property in your serviceProvider, defaults to user). essentially, this is the property that should be removed from your session to remove the user's information
  • accessTokenKey: this is the key of the accesstoken property inside your session, which should also be removed.
  • userInformation: this is an object that contains the information of the user that has been loggedout.
    • user: the id of the user,
    • timestamp: timestamp of logout. Could be used to ignore the request if the logout was long ago.

Available adapters

Existing adapters will be added here.

Example of an adapter implementation

// this is a non functional example,
function createAdapter(options) {
  const {
    connectionString
  } = options;

  const db = createConnection(connectionString);

  return function adapter(sessionKey, accessTokenKey, userInformation) {
    return new Promise((resolve, reject) => {
          const session = db.query({
        [`session.${sessionKey}.id`]: userInformation.user
    });

     // alter record and resave or do it in one query.
     // be aware that multiple sessions could have the same userId,
     // maybe we should also check whether the session is currently valid.

      return resolve();
    })
  }

  const authConfig = require('./authConfig');

  const adapter = createAdapter({
    connectionString: process.env.connectionString
  });

  Object.assign(authConfig, {
    logout: {
      adapter,
      securityHash: 'blabla'
    }
  });
}