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_SECRET
Do 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
.env
file - 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/login
triggers the authentication by calling Passport'sauthenticate
method. The user is then redirected to the tenant login page hosted by PlusAuth./auth/callback
is the route that the user is returned to by PlusAuth after authenticating. It redirects the user to the profile page (/user
)./profile
displays the user's profile./auth/logout
logs the user out of PlusAuth./auth/logout/callback
is 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.