I am looking for suggestion or Approach on the UseCase.
When my app launches, I have customer login to our application through SSO. This could be either Okta or EntraID or Any IDP provider. As they are browsing the application they click on a specific button on the Page (e.g A risky action like start RDP Session or Approve expense report), before they action takes place we want to users IDP show the second factor and validates the identity.
I am trying to use OAuth + OIDC Protocol as implement this. Is it possible or am i approaching it wrong.
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var session = require('express-session');
var passport = require('passport');
var qs = require('querystring');
var { Strategy } = require('passport-openidconnect');
const axios = require('axios');
// source and import environment variables
//require('dotenv').config({ path: '.okta.env' })
require('dotenv').config({ path: '.entra.env' })
const { ORG_URL, CLIENT_ID, CLIENT_SECRET } = process.env;
var indexRouter = require('./routes/index');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({
secret: 'CanYouLookTheOtherWay',
resave: false,
saveUninitialized: true
}));
app.use(passport.initialize());
app.use(passport.session());
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
let logout_url, id_token;
let _base = ORG_URL.slice(-1) == '/' ? ORG_URL.slice(0, -1) : ORG_URL;
console.log ('Base URL - ' + _base);
axios
.get(`${_base}/.well-known/openid-configuration`)
.then(res => {
if (res.status == 200) {
let { issuer, authorization_endpoint, token_endpoint, userinfo_endpoint, end_session_endpoint } = res.data;
logout_url = end_session_endpoint;
console.log ('Authorization Endpoint - ' +authorization_endpoint);
// Set up passport
passport.use('oidc', new Strategy({
issuer,
authorizationURL: authorization_endpoint,
tokenURL: token_endpoint,
userInfoURL: userinfo_endpoint,
clientID: CLIENT_ID,
clientSecret: CLIENT_SECRET,
callbackURL: '/authorization-code/callback',
scope: 'profile offline_access',
maxAge: 0
}, (issuer, profile, context, idToken, accessToken, refreshToken, params, done) => {
console.log(`OIDC response: ${JSON.stringify({
issuer, profile, context, idToken,
accessToken, refreshToken, params
}, null, 2)}n*****`);
id_token = idToken;
return done(null, profile);
}));
}
else {
console.log(`Unable to reach the well-known endpoint. Are you sure that the ORG_URL you provided (${ORG_URL}) is correct?`);
}
})
.catch(error => {
console.error(error);
});
passport.serializeUser((user, next) => {
next(null, user);
});
passport.deserializeUser((obj, next) => {
next(null, obj);
});
function ensureLoggedIn(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.redirect('/login')
}
app.use('/', indexRouter);
app.use('/login', passport.authenticate('oidc'));
app.use('/authorization-code/callback',
// https://github.com/jaredhanson/passport/issues/458
passport.authenticate('oidc', { failureMessage: true, failWithError: true }),
(req, res) => {
res.redirect('/profile');
}
);
app.use('/profile', ensureLoggedIn, (req, res) => {
res.render('profile', { authenticated: req.isAuthenticated(), user: req.user });
});
app.use ('/mfa', passport.authenticate('oidc', (req, res) =>{
res.redirect('/user');
});
app.post('/logout', (req, res, next) => {
req.logout(err => {
if (err) { return next(err); }
let params = {
id_token_hint: id_token,
post_logout_redirect_uri: '/'
}
res.redirect(logout_url + '?' + qs.stringify(params));
});
});
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message + (err.code && ' (' + err.code + ')' || '') +
(req.session.messages && ": " + req.session.messages.join("n. ") || '');
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
When the user click on the homepage login button, we redirect to Okta or EntraID. The user successfully completes the Authentication and we get the token which is stored in the Session and redirect them to the Profile page.
Homepage:
html
head
title= "Okta Redirect Flow With Express"
meta(charset="utf-8")
meta(name="viewport", content="width=device-width, initial-scale=1, shrink-to-fit=no")
link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css')
body#samples
nav.navbar.navbar-default
div.container-fluid
ul.nav.navbar-nav
a(href="/")
img(src="https://www.okta.com/sites/default/files/Okta_Logo_BrightBlue_Medium.png", height="50", style="padding: 10px;")
if authenticated
form.navbar-form.navbar-right(method="post", action="/logout")
button#logout-button.btn.btn-danger(type="submit") Logout
else
form.navbar-form.navbar-right(method="get", action="/login")
button#login-button.btn.btn-primary(type="submit") Login
div#content.container
block content
Profile Page.
extends layout
block content
h2= "My Profile"
br
div
p Hello, #{user.displayName}. Below is the information that was read with your #[a(href="https://developer.okta.com/docs/api/resources/oidc.html#get-user-information", target="_blank") Access Token].
br
table.table.table-striped
thead
tr
th Claim
th Value
tbody
for values, claims in user
tr
td #{claims}
td #{values}
br
div
form(method="post" action="/mfa")
button(type="submit") 2FA
In the profile page the user clicks on the 2FA button. Then I want to just invoke the Second factor which is already configured (sms or authenticator) in okta or EntraID to be popped up. Here i am calling the below method.
app.use ('/mfa', passport.authenticate('oidc', {
failureMessage: true,
failWithError: true,
}), (req, res) =>{
res.redirect('/user');
});
Since i am setting the maxage to zero. Its taking me back to login screen rather than just the 2FA. I am not sure how to proceed.
Any help will be greatly appreciated.