This tutorial shows how to use PlusAuth with ExpressJS. If you do not have a PlusAuth account, register from here .
Create PlusAuth Client
After you sign up or log in to PlusAuth, you need to create a client to get the necessary configuration keys in the dashboard. Go to Clients and create a client with the type of Regular Web Application
Configure Client
Get Client Properties
You will need your Client Id and Client Secret for interacting with PlusAuth. You can retrieve them from the created client's details.
Configure Redirect and Logout URIs
When PlusAuth authenticates a user, it needs a URI to redirect back. That URI must be in your client's Redirect URI list. If your application uses a redirect URI that is not white-listed in your PlusAuth Client, you will receive an error.
The same thing applies to the logout URIs. After the user logs out, you need a URI to be redirected.
Configure Node.js to use PlusAuth
Let's start to create an ExpressJS application.
We are using async functions on initializing the state. Below you will find snippets wrapped in Immediately-invoked Function Expressions (IIFE), but in the final state, there would be only one.
Create the .env file
Create the .env file in the root of your app and add your PlusAuth variables and values to it.
# .envPLUSAUTH_ISSUER_URL=YOUR_PLUSAUTH_DOMAINPLUSAUTH_CLIENT_ID=YOUR_CLIENT_IDPLUSAUTH_CLIENT_SECRET=YOUR_CLIENT_SECRETDo not put the .env file into source control. Otherwise, your history will contain references to your client's secret. If you are using git, create a .gitignore file (or edit your existing one, if you have one already) and add .env to it. The .gitignore file tells source control to ignore the files (or file patterns) you list. Be careful to add .env to your .gitignore file and commit that change before you add your .env
# .gitignore.env Install the dependencies
To get started, install the following dependencies.
- passport - an authentication middleware for Node.js
- openid-client - an PlusAuth authentication strategy for Passport
- express-session - a middleware to manage sessions
- dotenv - a module to load environment variables from a
.envfile - ejs - a simple yet powerful template engine for creating views
# installation with npmnpm install passport openid-client express-session dotenv ejs --save Configure Express Application
We will configure our Express application in a simple way. We will be using EJS as template engine.
// app.jsconst express = require("express");
(async () => { const app = express();
// view engine setup app.set("views", path.join(__dirname, "views")); app.set("view engine", "ejs");})(); Configure express-session
Include the express-session module and configure it in app.js. The secret parameter is a secret string that is used to sign the session ID cookie. Please use a custom value.
// app.js
const session = require('express-session');
(async ()=>{ // express-session config const sessionOptions = { secret: 'SomeRandomValue', // Change this to a random value resave: false, saveUninitialized: true };
if (process.env.NODE_ENV === 'production') { // Use secure cookies in production. More info at https://www.npmjs.com/package/express-session#cookiesecure sess.cookie.secure = true;
// Uncomment the line below if your application is behind a proxy (like on Nginx, Envoy, Heroku, etc.) // app.set('trust proxy', 1); }
app.use(session(sessionOptions));})(); Configure Passport with the application settings
Include the passport and openid-client modules in app.js. Configure Passport to use a PlusAuth Client with your settings. Use passport.initialize() and passport.session() to initialize Passport with persistent login sessions.
Passing the scope parameter to openid-client strategy with values openid email profile is necessary to access email and the other attributes stored in the user profile.
// app.js
// Load environment variables from .envrequire('dotenv').config();
const passport = require('passport');const { Issuer, Strategy } = require("openid-client");
(async () => { const PlusAuthIssuer = await Issuer.discover(process.env.PLUSAUTH_ISSUER_URL);
const PlusAuthClient = new PlusAuthIssuer.Client({ PLUSAUTH_CLIENT_ID: process.env.PLUSAUTH_CLIENT_ID, PLUSAUTH_CLIENT_SECRET: process.env.PLUSAUTH_CLIENT_SECRET, redirect_uris: ["http://localhost:3000/auth/callback"],
post_logout_redirect_uris: ["http://localhost:3000/auth/logout/callback"], response_types: ["code"], });
const PlusAuthStrategy = new Strategy( { client: PlusAuthClient, params: { scope: "openid email profile", }, passReqToCallback: true }, (req, token, user,done) => { // Store token in session req.session.token = token return done(null, user); } )
passport.use("PlusAuth", PlusAuthStrategy);
app.use(passport.initialize()); app.use(passport.session());})();Please make sure you add passport middlewares in your code after the express middleware (app.use(session(sessionOptions)).
Storing and retrieving user data from the session
In a typical web application, the credentials used to authenticate a user are only transmitted during the login request. If authentication succeeds, a session would be established and maintained via a cookie set in the user's browser. Each subsequent request does not contain credentials but rather the unique cookie that identifies the session.
To support login sessions, Passport serializes and deserializes user instances to and from the session. Optionally, you may want to serialize only a subset to reduce the footprint, i.e., user.id.
// app.js
// You can use this section to keep a smaller payloadpassport.serializeUser(function (user, done) { done(null, user);});
passport.deserializeUser(function (user, done) { done(null, user);}); Middleware to protect routes
Create an isLoggedIn middleware to protect routes and ensure they are only accessible if logged in.
// app.jsfunction isLoggedIn(req, res, next) { if (req.isAuthenticated()) { return next(); }
res.redirect("/auth/login");} Implement login, user profile, and logout
In this example, the following routes are implemented:
/auth/logintriggers the authentication by calling Passport'sauthenticatemethod. The user is then redirected to the tenant login page hosted by PlusAuth./auth/callbackis the route that the user is returned to by PlusAuth after authenticating. It redirects the user to the profile page (/user)./profiledisplays the user's profile./auth/logoutlogs the user out of PlusAuth./auth/logout/callbackis the route that the user is returned to by PlusAuth after logging out.
Adding the authentication routes
Below, you will find routes related to authentication.
// app.js
app.use("/auth/login", passport.authenticate("PlusAuth"));
app.use( "/auth/callback", passport.authenticate("PlusAuth", { failureMessage: true, failureRedirect: "/error", successRedirect: "/profile", }));
app.get("/auth/logout", (req, res) => { res.redirect( PlusAuthClient.endSessionUrl({ id_token_hint: req.session.token.id_token }) );});
app.get("/auth/logout/callback", (req, res) => { req.logout(); res.redirect("/");}); Create the user profile route
The /profile route (the user's profile) should only be accessible if the user is logged in. We will be using authentication middleware we created in the step Middleware to protect routes
// app.jsapp.use("/profile", isLoggedIn, (req, res) => { res.render("profile", { user: req.user });}); Index route
Let's create an index route to serve our application's homepage.
// app.jsapp.get("/", function (req, res) { res.render("index", { user: req.user });}); Making the user available in the views
In the views and layouts, it is often necessary to conditionally render content depending on if a user is logged in or not. Other times, the user object might be required to customize the view.
Create a middleware lib/middleware/userInViews.js for this purpose.
// userInViews.js
module.exports = function () { return function (req, res, next) { res.locals.user = req.user; next(); };}; Create Views
Homepage
Create a views/index.ejs template.
<!-- views/index.ejs -->
<%- include('header'); %><div class="jumbotron"> <div class="container"> <h1 class="display-3">Hello, world!</h1> <p>This is a template for a simple login/register system.</p> <% if(user) { %> <a class="btn btn-success btn-lg" href="/profile" role="button">View Profile »</a> <% }else { %> <p>To view Profile page please login.</p> <a class="btn btn-primary btn-lg" href="/auth/login" role="button">Login/Register »</a> <% } %> </div></div><%- include('footer'); %> User Profile
Create a views/profile.ejs template.
<!-- views/profile.ejs -->
<%- include('header'); %><div class="container"> <h3>Welcome <%= user.email %>!</h3> <pre>User object: <%= JSON.stringify(user, null, 3) %></pre></div><%- include('footer'); %> See it in action
That's it. Start your app and point your browser to http://localhost:3000 . Follow the Log In link to log in or sign up to your PlusAuth tenant. Upon successful login or signup, you should be redirected back to the application.