Node.js with Express
This is a sample application showing how to configure and enable OpenID Connect middleware to use with Criipto Verify in a Node.js web application with Express, openid-client and Passport.
Four steps are required to complete your first test login:
- Register your application in Criipto Verify
- Configure your OAuth2 flow
- Configure your application to use Criipto Verify
- Trigger authentication in your application
This explains how to set up your application and test with test users. To use real e-IDs for login the setup is the same, but you must be set up for Production
And note that you need test e-ID users to see your code in action. How to get those is described further down.
You may get a completed and ready to run sample from GitHub showing the below recipe in the simplest of Node.js Express applications.
To modify your existing application to work with Criipto Verify, follow the steps below.
Register Your Application in Criipto Verify
After you signed up for Criipto Verify, you must register an application before you can actually try logging in with any e-ID.
Once you register your application you will also need some of the information for communicating with Criipto Verify. You get these details from the settings of the application in the dashboard.
Specifically you need the following information to configure you application
- Client ID to identify you application to Criipto Verify. In the case below we chose
urn:criipto:nodejs:demo:1010
- Domain on which you will be communicating with Criipto Verify. Could be for example
nodejs-sample.criipto.id
Register callback URLs
Before you can start sending authentication requests to Criipto Verify you need to register the URLs on which you want to receive the returned JSON Web Token, JWT.
The Callback URL of your application is the URL where Criipto Verify will redirect to after the user has authenticated in order for the OpenID Connect middleware to complete the authentication process.
You will need to add this URL to the list of allowed URLs for your application. The Callback URL for the sample project is http://localhost:3000/auth/callback and http://localhost:3000/logout/callback so be sure to add this to the Callback URLs section of your application. Put each URL on a new line.
If you deploy your application to a different URL you will also need to ensure to add that URL to the Callback URLs.
Configure the OAuth2 code flow
If you are registering a new application, you must first save the configuration.
Once you have a saved application registration you may configure the OAuth2 code flow.
Open the application registration and configure it for the right OAuth2 flow:
- Enable OAuth2 code flow
- Copy the generated client secret.
- Set the user info response strategy to
plainJson
to enable retrieval of plain JSON user information from the/oauth2/userinfo
endpoint.
Note that this is the only time you will be shown the actual value of the client secret. Criipto only stores this as a hashed value, which means you cannot retieve the value once it has been generated and stored.
You may configure Criipto Verify to retrieve the user information from either the userinfo
endpoint - the default option - or you may explicitely choose the fromTokenEndpoint
in the user info response strategy instead:
Configure Your Application to Use Criipto Verify
Install dependencies
For this sample application you will be using Node.js with Express. If you are building a new Express application from scratch, consider using Express generator to quickly create an application skeleton.
On top of Express, you will need to install openid-client, a server side OpenID implementation for Node.js, and Passport, Express-compatible authentication middleware. Also make sure to install express-session and connect-ensure-login packages which provide a way to establish a user session and protect private routes.
npm install openid-client
npm install passport
npm install express-session
npm install connect-ensure-login
Configure openid-client
Got to app.js file where your Express app is initialized and import Issuer
from openid-client.
const { Issuer } = require('openid-clien');
Call discover
method of Issuer
and pass it your Criipto domain as an argument. This will return a promise which will, after resolved, provide a Criipto Issuer instance. From Criipto Issuer instance you can create a new client by calling a Client
constructor and passing in a settings object which must include following properties:
client_id
: Criipto Client ID/Realm, for exampleurn:criipto:nodejs:demo:1010
client_secret
: Generated in second step, for examplej9wYVyD3zXZPMo3LTq/xSU/sMu9/shiFKpTHKfqAutM=
redirect_uris
: An array of post-login redirect URIs, for example["http://localhost:3000/auth/callback"]
post_logout_redirect_uris
: An array of post-logout redirect URIs, for example["http://localhost:3000/logout/callback"]
token_endpoint_auth_method
: Requested authentication method for the token endpoint, for exampleclient_secret_post
Issuer.discover('https://nodejs-sample.criipto.id')
.then(criiptoIssuer => {
const client = new criiptoIssuer.Client({
client_id: 'urn:criipto:nodejs:demo:1010',
client_secret: 'j9wYVyD3zXZPMo3LTq/xSU/sMu9/shiFKpTHKfqAutM=',
redirect_uris: [ 'http://localhost:3000/auth/callback' ],
post_logout_redirect_uris: [ 'http://localhost:3000/logout/callback' ],
token_endpoint_auth_method: 'client_secret_post'
});
// you can optionally set clock_tolerance to allow JWT to be valid
// even if your system is not in sync with the server time
client[custom.clock_tolerance] = 300 // seconds
// do the rest of setup here
});
Optionally, you can set clock_tolerance
option for openid-client, to allow JWT to be valid even if your system is not in sync with the server time.
client[custom.clock_tolerance] = 300 // seconds
Configure Passport and Express session
Next step is to initialize Passport (authentication middleware) and Express session. Start by importing required modules.
const expressSesssion = require('express-session');
const passport = require('passport');
It is important to set up an Express session before initializing Passport. Add the following code to your app.js file.
app.use(
expressSesssion({
secret: 'Some secret you say?',
resave: false,
saveUninitialized: true
})
);
app.use(passport.initialize());
app.use(passport.session());
After you have initialized Passport, you have to tell it which strategy (authentication mechanisms) to use with your application. Openid-client provides a strategy that you can use with the client, so make sure to import it from openid-client.
const { Strategy } = require('openid-client');
When passing a strategy to the passport, you have to pass two arguments to the use
method: the strategy name and initialized strategy object. To initialize the strategy imported from opeid-client, you have to call a constructor with two arguments: settings object containing at least the initialized client and a callback function to execute after you receive user information from Criipto Verify.
Add the following code to your app.js file.
passport.use(
'oidc',
new Strategy({ client }, (tokenSet, userinfo, done) => {
return done(null, tokenSet.claims());
})
);
The last thing you need to finish setting up the passport is to provide a function to serialize and deserialize a user. Add the following code to your app.js file.
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
Trigger authentication in your application
Choosing the specific login method
Criipto Verify supports a range of country and bank specific e-ID services. They are all accessed through the same endpoints, e.g. https://<YOUR COMPANY>.criipto.id/oauth2/authorize
To pick the login method you must set the acr_values
parameter on the authentication request in order to choose the type of authentication you want. How you set this query string parameter varies with programming platform and your OpenID Connect library of choice.
The current list of possible values is:
Login method | acr_values |
---|---|
Norwegian BankID | |
Mobile or Web (user choice): | urn:grn:authn:no:bankid |
Norwegian Vipps Login | |
Login with Vipps app: | urn:grn:authn:no:vipps |
Swedish BankID | |
Same device: | urn:grn:authn:se:bankid:same-device |
Another device (aka mobile): | urn:grn:authn:se:bankid:another-device |
Danish NemID | |
Personal with code card: | urn:grn:authn:dk:nemid:poces |
Employee with code card: | urn:grn:authn:dk:nemid:moces |
Employee with code file: | urn:grn:authn:dk:nemid:moces:codefile |
Finish e-ID | |
BankID: | urn:grn:authn:fi:bank-id |
Mobile certificate (Mobiilivarmenne): | urn:grn:authn:fi:mobile-id |
Any of the two: | urn:grn:authn:fi:all |
Itsme | |
Basic: | urn:grn:authn:itsme:basic |
Advanced: | urn:grn:authn:itsme:advanced |
Belgium | |
Verified e-ID | urn:grn:authn:be:eid:verified |
Germany | |
Sofort (with Schufa check) | urn:grn:authn:de:sofort |
In the sample, we have created a form which will put the chosen login option in a request parameter and redirect a user to the authentication route specified.
<select class="form-control" name="loginmethod" form="loginForm">
<option value="urn:grn:authn:no:bankid">Norwegian BankID</option>
<option value="urn:grn:authn:no:vipps">Norwegian Vipps</option>
<option value="urn:grn:authn:se:bankid:same-device">Swedish BankID same device</option>
<option value="urn:grn:authn:se:bankid:another-device">Swedish BankID another device</option>
<option value="urn:grn:authn:dk:nemid:poces">Danish NemID personal with code card</option>
<option value="urn:grn:authn:dk:nemid:moces">Danish NemID employee with code card</option>
<option value="urn:grn:authn:dk:nemid:moces:codefile">Danish NemID employee with code file</option>
<option value="urn:grn:authn:fi:bank-id">Finish e-ID - BankID</option>
<option value="urn:grn:authn:fi:mobile-id">Finish e-ID - Mobile certificate</option>
<option value="urn:grn:authn:fi:all">Finish all options</option>
</select>
<form action="/auth" id="loginForm">
<input class="btn btn-outline-primary" type="submit" value="Login">
</form>
Start the authentication request
After you submit a form, you will be redirected to the /auth
route. Now you have to set up an Express middleware for the /auth
route where you will tell passport to start the authentication request and redirect the user to the chosen login option.
To do that, you have to invoke authenticate
method and tell it which strategy to use. You also have to provide an object with acr_values
and optionally some other additional parameters.
Add the following code to your app.js file.
app.get('/auth', (req, res, next) => {
let loginMethod = req.query.loginmethod;
passport.authenticate('oidc', { acr_values: loginMethod })(req, res, next);
});
This will redirect the user to the authentication URL. If you want to show the authentication page in an iframe, you can simply create an iframe with a name
attribute and set the target of the login form to the name of the iframe. Just make sure that the iframe is generated in the DOM before executing the form.
<form action="/auth" id="loginForm" target="loginIframe">
<input class="btn btn-outline-primary" type="submit" value="Login">
</form>
<iframe class="login-iframe" name="loginIframe"></iframe>
Set up authentication redirect route
After successful authentication with Criipto Verify, you will be redirected to the authentication redirect route which you provided when you initialized the openid-client. If you haven’t already, make sure that the route is registered in Callback URLs section in Criipto Verify. For this sample application, the redirect route is /auth/callback
so you have to create an Express route which will handle the callback and authenticate the user in your application.
To do that, you simply have to call authenticate
method of the passport and pass it the strategy name and an object containing following properties:
successRedirect
: where to redirect if the authentication was successfulfailureRedirect
: where to redirect if the authentication was not successful
app.get('/auth/callback', (req, res, next) => {
passport.authenticate('oidc', {
successRedirect: '/success',
failureRedirect: '/error'
})(req, res, next);
});
If you are redirecting a user to the authentication URL, then you can set successRedirect
directly to the private rout, for example /users
, but if you are doing the authentication inside of an iframe, you will have to create an additional route, for example /success
, which will load a JavaScript code to break out of an iframe and redirect the user on the client side. In the sample application, as soon as the /success
route loads, we are calling a redirect on the top window.
window.top.location.replace('/users');
Protecting the private route
You can use connect-ensure-login middleware to protect the private route and redirect the user to the login route if they are not authenticated. To do that, first import ensureLoggedIn
method from the connect-ensure-login middleware.
const ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn;
Then you can simply use the method with Express Router for the protected route. You can specify where to redirect the user if they are not authenticated by passing the desired route as an argument of ensureLoggedIn
method.
router.get('/', ensureLoggedIn('/'), (req, res) => {
res.render('users', req.user);
});
Set up a user logout
The logout process consists of two parts: sending the request to Criipto Verify to end the session and clearing the user from the local storage.
First you will have to create a /logout
route to send the request to Criipto Verify to end the session. You can retrieve the logout redirect URL from the client by calling endSessionUrl
method.
app.get('/logout', (req, res) => {
res.redirect(client.endSessionUrl());
});
When Criipto Verify successfuly ends the session, you will be redirected to the post-logout URL that you have provided when initializing the client.
Create a post-logout route where you have to invoke logout
method to ensure that the passport clears the user from the local storage. Now the logout process is completed and you can redirect the user to the public route.
app.get('/logout/callback', (req, res) => {
req.logout();
res.redirect('/');
});
Test users
Almost all e-ID types have a notion of test users and real users.
Real users are real people logging in to a web site, thus voluntering their real name and typically also a social security number, SSN.
Test users are either created by you for the occasion, or we provide you with access to already created test users.
You may ready more in the section on test users
The runtime flow
In summary, the steps above will lead to a runtime flow that looks like this:
- The web server starts the application which configures and initializes the openid-client and Passport middleware. The openid-client is configured with a URL from which it retrieves the metadata describing the various endpoints and encryption keys, such as the token and userinfo endpoints as well the token signing certificates.
- The user picks the login method, or the application is hardcoded to one of the authentication options
- Posting the login form to the authentication route
/auth
will trigger the authentication flow. - The user’s browser is redirected to the Criipto Verify service where actual login happens.
- A callback with an issued authorization code is sent back to the application and intercepted by the middleware.
- The middleware calls the Criipto Verify service to exchange the code for an access token. Note that this is a direct server to server call which - unlike the other communication - does not pass through the browser.
- The access token is used by the middleware to retrieve the available user information which is set as claims on the user principal.
If you want to inspect what is actually going on you may see much of it if you use for example Chrome and turn on the developer tools to inspect the network traffic.
Tracing the flow in the demo application
By default, our Demo application will log important steps of the authentication flow to the console. You can turn it off, or change the logging level, by setting DEBUG
environment variable in .env
file to desired value.
Setting up for Production
Once you have integrated with Criipto Verify and tested that it works with test user accounts, you are ready to go to production to accept real e-ID logins and signatures.
Please note that for production usage a paid subscription is required.
Read more in the section on how to set up for production.