Your guide to passwordless authentication with node.js

LoginID
6 min readJan 14, 2021

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.
Integration options on the LoginID dashboard
Dashboard view of a registered LoginID account

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.

--

--

LoginID

LoginID is a comprehensive Passkeys + FIDO-based multi factor authentication solution that offers frictionless biometric authentication at low cost.