Authentication strategy in the client application

Please refer to the passport-oauth2 module documentation.

The Auth App provides OAuth2 authentication to client application. Here we present a simple Nodejs express server application .

The OAuth 2.0 authentication strategy authenticates users using a third-party account and OAuth 2.0 tokens. The provider's OAuth 2.0 endpoints, as well as the client identifier and secret, are specified as options. The approach requires a verify callback, which receives an access token and profile, and calls cb providing a user.

const express = require('express');
const expressSession = require('express-session');
const cookieParser = require('cookie-parser');
const ensure = require('ensure-login')
const PORT = process.env.PORT || 5000;
const SECRET = process.env.SECRET || 'i am your deepest secret sweetheart'
const passport = require('passport');
const OAuth2Strategy = require('passport-oauth2');

/**
 * Oauth2 setup
 *
 * The clierntID and clientSecret must be set according to the state51__Application
 * record in the platform
 *
 * @type {OAuth2Strategy}
 */
const oauth2Strategy = new OAuth2Strategy(
  {
    authorizationURL: 'https://auth.openimp.com/dialog/authorise',
    tokenURL: 'https://auth.openimp.com/oauth/token',
    clientID: process.env.CLIENT_ID || 'sample_client_application_id',
    clientSecret: process.env.CLIENT_SECRET || 'sample_client_application_secret',
    callbackURL: "/login/openimp/callback"
  },

  /**
   * Callback that makes sure, that the identity and tokens received from the Auth App
   * are acceptable for this application. If the application uses some notion of user persistence,
   * then the persistent user should be retrieved from a store a return into the callback.
   */
  function (accessToken, refreshToken, profile, cb) {
    const user = {
      username: profile.identifier,
      profile,
      accessToken,
      refreshToken
    }
    return cb(null, user);
  }
)

/**
 * Custom function to load userProfile from the Auth App
 */
oauth2Strategy.userProfile = function(accessToken, done) {
  let self = this;
  this._oauth2.get('https://auth.openimp.com/api/userinfo', accessToken, function (err, body, res) {
    let json;

    if (err) {
      if (err.data) {
        try {
          json = JSON.parse(err.data);
        } catch (_) {}
      }
      return done(new Error('Failed to load user profile: ', json.toString()));
    }

    try {
      json = JSON.parse(body);
    } catch (ex) {
      return done(new Error('Failed to parse user profile'));
    }
    done(null, json);
  });
}

/**
 * Inject the strategy into the passport middleware
 */
passport.use(oauth2Strategy)

/**
 * what to store in the session - so far we store the whole profile. Once the system gets its own
 * persistent representation of a user then it should store only the user ID in the session
 */
passport.serializeUser(function(user, done) {
  return done(null, user);
});

/**
* and when restoring the session provides the ID and then the application backend (database, nosql storage,
* whatever) provides the rest. So far we only rely on the
*/
passport.deserializeUser(function(id, done) {
  return done(null, id)
});

/**
 * Setup the Express server
 */
const main = async () => {
  const app = express();

  app.use(express.urlencoded({extended: false}));
  app.use(cookieParser());

  // we need session for storing user details
  app.use(
    expressSession({
      saveUninitialized: true,
      resave: true,
      name: 'frontend.sid',
      secret: SECRET
    })
  )

  app.use(passport.initialize());
  app.use(passport.session());

  /**
   * Login path
   */
  app.get('/login', passport.authenticate('oauth2'))


  /**
   * The login callback, the user is redirected to theis callback
   * from the Auth App
   */
  app.get('/login/openimp/callback', passport.authenticate('oauth2', {
    successRedirect: '/auth',
    failureRedirect: '/error'
  }))

  /**
   * Trivial logout and session destruction
   */
  app.get('/logout', (req, res) => {
    req.logout();
    req.session.destroy(() => {
      res.redirect('/');
    })
  });


  app.get('/', (req, res) => {
    res.send( {
      title: 'Public section',
      username: req.user? req.user.username : 'unknown'
    })
  })

  /**
   * here se setup an authorised namespace
   */
  const authorisedNamespace = express.Router()

  /**
   * The ensure login middleware makes sure, that all requests to to routes in this Express Router
   * must be authorised
   */
  authorisedNamespace.use(
    ensure.ensureLoggedIn({baseUrl: '/', redirectTo: '/login/openimp', setReturnTo: /^([^/]*|.*\/)[^.]+$/})
  )

  authorisedNamespace.get('/', (req, res) => {
    res.send({
      title: 'Authorised section',
      username: req.user.username
    })
  });

  /**
   * Route with details about the user in the session
   */
  authorisedNamespace.get('/user', (req, res) => {
    res.send({
      title: 'User information ',
      username: req.user.username,
      accessToken: req.user.accessToken,
      profile:  JSON.stringify(req.user.profile, null, 4)
    })
  });


  // and mount it into the main app
  app.use('/auth', authorisedNamespace)

  app.listen(PORT, () => {
    console.log('server started! Listening on port ' + PORT);
  });
};

main()
  .catch((err) => {
    console.error(err);
  });

Created: Fri Mar 29 2024 01:09:58 GMT+0000 (Coordinated Universal Time)
Build ID: 120390