This guide will provide an example code and a general explanation on how to create an OIDC integration from scratch. You should have Node.js 10 or later installed on your local environment and you need to have rudimentary knowledge on modern Javascript. This guide will use passport.js with its oauth2 strategy as well as express to facilitate the integration.
Step 1 — Obtain your client keys
To process your authentication requests, you need to create a set of credentials in order to identify which app a user is attempting to authenticate into. These credentials are outlined as your APPID
and your APPSECRET
.
To obtain the credentials you will need to perform the following steps:
Create a new admin account
- Navigate to https://usw1.loginid.io/en/register and select the “Register a new account” option and follow the instructions to create a new account on LoginID’s dashboard.
- Use the navigation bar to select “Integration” and choose OIDC type.
Your dashboard should look something like this.
Create Credentials
In the resulting form, you must enter the following information:
- App Name
- Callback URI
Please note that for the purpose of this guide we will be using http://localhost:9876/callback
for the callback; your own custom application may use another URI.
For security reasons, if the CallbackURL is incorrect then your API calls will fail.
Once you create a new integration you will be provided with the following on your dashboard:
- The APP ID is used to identify your application
- APP SECRET is used to validate claims
Please copy down and record the APP ID and APP SECRET because this will not be shown again later. You will need these values for creating the demo app.
Step 2 — Node-Express Setup
Dependencies and Environment
Open up a terminal/command prompt in your folder of choosing and initialize npm
npm init
The next thing you would like to add to this folder would be an .env
file. This file would contain information that should not be committed to version control, and may be changed depending on environment. You would want to fill it with the following information.
LOGIN_URI=https://oauth2.usw1.loginid.io #replace usw1 with another region if you are not in north americaLOGIN_REDIRECT_URI=http://localhost:9876/callbackLOGIN_SCOPES=openidLOGIN_APPID=<your app ID>LOGIN_APPSECRET=<your app Secret>PORT=9876 #important, port number must match the port in callbackURI above
Basic Express application
This section will describe how to setup a basic express application, if you already know how to do that skip to Step 3 — OIDC Integration.
Let us install the dependencies we need with the following command:
npm i base64url express express-session passport passport-oauth2 dotenv cookie-parser connect-ensure-login
Let us now setup some fundamental code for a basic web server. Lets create our main file and name it app.js
touch app.js
Let us import all our libraries and create the express server in app.js
require("dotenv").config();const base64url = require("base64url");const express = require("express");const session = require("express-session");const cookie = require("cookie-parser");const passport = require("passport");const OAuth2Strategy = require("passport-oauth2");const { ensureLoggedOut, ensureLoggedIn } = require("connect-ensure-login");const app = express();const port = process.env.PORT || 9876;app.listen(port, () => {console.log(`listening on port ${port}`);});
Then, add some routes to this server:
app.get("/", (req, res) => {res.send("Homepage<br/><a href='/login'>Login</a>");});app.get("/login", (req, res) => {res.send("login");});app.get("/callback", (req, res) => {res.send("callback uri");});app.get("/dashboard", (req, res) => {res.send("dashboard <br><a href='/logout'>logout</a>");});app.get("/logout", (req, res) => {res.send("logout");});
In order to run this, we will be using nodemon
to monitor changes.
npm i --save-dev nodemon
Then add the following line to the “scripts” object of your package.json
"start": "nodemon app.js"
Then you can type npm run start
in your console and visit the 5 routes that we just set up.
http://localhost:9876/http://localhost:9876/loginhttp://localhost:9876/callbackhttp://localhost:9876/dashboardhttp://localhost:9876/logout
Now that all the setup work has been completed we are ready to start integrating.
Step 3 — OIDC Integration
Setting up the authentication middleware
We will be using passport.js
and connect-ensure-login
middleware here to facilitate our login session management.
Let us add those to our express app:
app.use(cookie());app.use(session({secret: "keyboard cat", // change the secret when in productionresave: false,saveUninitialized: true}));app.use(passport.initialize());app.use(passport.session());passport.use(new OAuth2Strategy(options, verify)); // important linepassport.serializeUser((user, done) => {// note that the user param is whatever you passed into the done() function// in the verify function you defined earlierdone(null, user);});passport.deserializeUser((user, done) => {// the user param here is what you have stored in the sessiondone(null, user);});
For authentication strategy, we will be using the OAuth2Strategy
According to the docs for Oauth2Strategy we will need to configure an object containing the configuration options as well as a function which validate the authentication, as could be seen in the following code snippet:
const options = {clientID: process.env.LOGIN_APPID,clientSecret: process.env.LOGIN_APPSECRET,callbackURL: process.env.LOGIN_REDIRECT_URI,authorizationURL: `${process.env.LOGIN_URI}/oauth2/auth`,tokenURL: `${process.env.LOGIN_URI}/oauth2/token`,scope: process.env.LOGIN_SCOPES,state: base64url(JSON.stringify({ state: process.env.LOGIN_APPID })),passReqToCallback: true // this is important, so you can obtain the bearer token in the verify() function};const verify = (req, accessToken, refreshToken, params, profile, done) => {console.log(`Access token is: ${accessToken}`);console.log(`Refresh token is: ${refreshToken}`);console.log("Params: ", params["token_type"], params["id_token"]);console.log("Profile:", profile);// note due to the way passport works, profile would always be {} unless// you override the default strategy.userProfile function (see below)if (profile) {// TODO; do something here validate the token's signature.const user = profile;return done(null, user);}return done(null, false);};
Login
We have created 5 routes in Step 2. Let us make some changes to the login route and callback route so we could actually log a user in.
Replace the login route with the following:
app.get("/login", passport.authenticate("oauth2"));
and replace the callback route with the following:
app.get("/callback",passport.authenticate("oauth2", {session: true,successReturnToOrRedirect: "/dashboard"}));
Now when you visit /login you should be redirected to LoginID to authenticate, and on success be sent to /callback and finally to /dashboard if successful.
For this demo, you could see the Access Token and Bearer Token in the console.
LoginID does not supply a Refresh Token, it is your app’s responsibility to manage sessions for your users.
Logout
Of course, the user must also be able to logout. replace your logout route with the following:
app.get("/logout", (req, res) => {req.logout();res.redirect("/");// alternatively, you could do the following// req.session.destroy(() => res.redirect('/'));});
Protecting Routes
Currently, the dashboard & logout routes could be accessed without being logged in and the login route could be accessed when logged in, we don’t want that, this is where we will use connect-ensure-login
to ensure manage sessions. (you could write your own if you want).
Our login endpoint would now look something like this:
app.get("/login",ensureLoggedOut("/dashboard"),passport.authenticate("oauth2"));
ensureLoggedOut
will redirect the /login route to /dashboard if the user is logged in, no point logging in if you are already logged in.
The same goes for /dashboard:
app.get("/dashboard", ensureLoggedIn("/login"), (req, res) => {res.send("dashboard <br><a href='/logout'>logout</a>");});
ensureLoggedIn
will redirect the user to /login if they hit /dashboard without authentication
What about Registration?
LoginID OIDC handles that for you.
Step 4 — Getting User Profile
Up until this point if you looked into console.log in the terminal you will notice that profile is an empty string. That is because passport’s oauth2 strategy doesn’t actually provide a standard userProfile function. So we will need to make one ourselves.
Find this following line:
passport.use(new OAuth2Strategy(options, verify)); // important line
and replace it with the following:
const strategy = new OAuth2Strategy(options, verify);strategy.userProfile = function (accessToken, done) {this._oauth2._request("GET",`${process.env.LOGIN_URI}/userinfo`,null,null,accessToken,(err, data) => {if (err) {return done(err);}try {data = JSON.parse(data);} catch (e) {return done(e);}done(null, data);});};passport.use(strategy);
Notice what we did here. We added the userProfile function to the strategy and in the function we are calling the loginid userinfo route and using the access token to obtain the user profile. Now login again, and your console should log a profile something like the following
Profile: { sid: 'b6230b39-b1e8-42c6-8379-df60050b9a19', sub: 'test1@loginid.io' }
You can then use this information to serve content appropriately.
Live Demo
Click this link if you would like to see it live in action: https://codesandbox.io/s/brave-meadow-1swdt?from-embed
Getting help
We’re happy to assist, if you have any questions please don’t hesitate to contact dev@loginid.io.
About LoginID
LoginID is a comprehensive FIDO-based multifactor authentication solution that offers frictionless authentication. Created with developers and enterprises in mind, LoginID is FIDO-certified and adheres to PSD2 principles. With an implementation time of just one hour, LoginID’s multifactor authentication solution is a quick, simple to integrate, cost-effective, and regulatory friendly tool to give your business peace of mind around security, allowing you to focus on growing your business.
Get started for free here.