Integrating Passport.js with Logto

A hands-on guide and example to integrate Passport.js with Logto.
Sijie
SijieDeveloper
December 05, 20235 min read
Integrating Passport.js with Logto

Passport.js is an authentication middleware for Node.js that can be unobtrusively dropped into any Express-based web application. This comprehensive guide will focus on utilizing the passport-openidconnect plugin, offering a simple yet effective way to incorporate Logto with Passport.js. Throughout this tutorial, we'll be using Express.js to build our application. All the code we'll be discussing is available in a public GitHub repository.

Setting up express with session

Before diving into the integration process, we'll need to setup the basic project with installing Express.js and its session middleware.

Assuming you have a TypeScript project environment prepared (if not, refer to the official TypeScript documentation), begin by installing the necessary packages:

npm i cookie-parser express-session

Prepare the main file

Create src/app.ts with the following code:

import http from 'node:http';

import cookieParser from 'cookie-parser';
import express from 'express';
import session from 'express-session';

const app = express();
app.use(cookieParser());
app.use(
  session({
    secret: 'randome secret',
    cookie: { maxAge: 14 * 24 * 60 * 60 * 1000 },
    resave: false,
    saveUninitialized: false,
  })
);

const server = http.createServer(app);
server.listen(3000, () => {
  console.log('Sample app listening on http://localhost:3000');
});

This script initializes the Express app and configures cookieParser and session middleware for cookie-based session management, crucial for storing authentication results in Passport.js. It then uses the http module to launch the service.

Creating a Logto application

To proceed, a Logto application is necessary. Create one by visiting the Logto Console, navigating to “Applications”, and clicking “Create application”. Select “Express”, name your application, and click “Create application”.

Create application

After completing or finishing reading the creation guide, you'll find a detailed page with configuration information for the next steps.

Application detail

Setting URIs

On the application details page, configure two values:

  1. Redirect URIs: Set this to http://localhost:3000/callback to align with the project's callback route.
  2. Post Sign-out Redirect URIs: Use http://localhost:3000 for simplicity, directing users to the homepage post sign-out.

You can change these values later.

Configure Passport.js with the application settings

Install dependencies

Install passport and the OIDC strategy plugin, passport-openidconnect:

npm i passport passport-openidconnect

Prepare config file

Create app/config.ts for configuration management:

const appId = process.env.APP_ID;
const appSecret = process.env.APP_SECRET;
const endpoint = process.env.ENDPOINT;
const cookieSecret = process.env.COOKIE_SECRET ?? 'keyboard cat';

export const config = {
  appId,
  appSecret,
  endpoint,
  cookieSecret,
};

Set up the environment variables accordingly:

Environment VariableDescriptionExample
APP_IDApp ID from Logto4ukboxxxxxxxxx
APP_SECRETApp Secret from Logto5aqccxxxxxxx
ENDPOINTLogto Endpointhttps://g5xxx.logto.app/

Initialize Passport.js with OIDC strategy

Create src/passport.ts

import passport from 'passport';
import OpenIDConnectStrategy, { type Profile, type VerifyCallback } from 'passport-openidconnect';

import { config } from './config.js';

const { appId, appSecret, endpoint } = config;

export default function initPassport() {
  passport.use(
    new OpenIDConnectStrategy(
      {
        issuer: `${endpoint}/oidc`,
        authorizationURL: `${endpoint}/oidc/auth`,
        tokenURL: `${endpoint}/oidc/token`,
        userInfoURL: `${endpoint}/oidc/me`,
        clientID: appId,
        clientSecret: appSecret,
        callbackURL: '/callback',
        scope: ['profile', 'offline_access'],
      },
      (issuer: string, profile: Profile, callback: VerifyCallback) => {
        callback(null, profile);
      }
    )
  );

  passport.serializeUser((user, callback) => {
    callback(null, user);
  });

  passport.deserializeUser(function (user, callback) {
    callback(null, user as Express.User);
  });
}

This code initializes Passport with the OpenIDConnectStrategy. The serialize and deserialize methods are set for demonstration purposes.

Ensure to initialize and attach Passport middleware in your application:

...
initPassport();

const app = express();
app.use(cookieParser());
app.use(
  session({
    secret: 'randome secret',
    cookie: { maxAge: 14 * 24 * 60 * 60 * 1000 },
    resave: false,
    saveUninitialized: false,
  })
);
app.use(passport.authenticate('session'));
...

Building authentication routes

We'll now create specific routes for authentication processes:

Sign in: /sign-in

app.get('/sign-in', passport.authenticate('openidconnect'));

This route builds and redirects to an OIDC auth route.

Handle sign in callback: /callback

app.get(
  '/callback',
  passport.authenticate('openidconnect', {
    successReturnToOrRedirect: '/',
  })
);

This handles the OIDC sign-in callback, stores tokens, and redirects to the homepage.

Sign out: /sign-out

app.get('/sign-out', (request, response, next) => {
  request.logout((error) => {
    if (error) {
      next(error);
      return;
    }
    response.redirect(`${endpoint}/oidc/session/end?client_id=${appId}`);
  });
});

This redirects to Logto's session end URL, then back to the homepage.

Fetching auth state and protect routes

Develop the homepage with auth state:

app.get('/', (request: Request, response) => {
  const { user } = request;
  response.setHeader('content-type', 'text/html');

  if (user) {
    response.end(
      `<h1>Hello Logto</h1><p>Signed in as ${JSON.stringify(
        user
      )}, <a href="/sign-out">Sign Out</a></p>`
    );
  } else {
    response.end(`<h1>Hello Logto</h1><p><a href="/sign-in">Sign In</a></p>`);
  }
});

Here, user information is displayed using JSON.stringify, and the existence of request.user is used to protect routes.

Conclusion

Congratulations on integrating Passport.js with Logto. Hope this guide can help you migrate from existing systems to using Logto. For an enhanced authentication experience, consider trying out Logto Cloud today. Happy coding!