Skip to content

Commit

Permalink
Add express session maxAge
Browse files Browse the repository at this point in the history
  • Loading branch information
bgavrilMS committed Oct 23, 2023
1 parent 3e4c956 commit 65b8545
Showing 1 changed file with 134 additions and 85 deletions.
219 changes: 134 additions & 85 deletions samples/msal-node-samples/auth-code-distributed-cache/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,34 @@
import fs from "fs";
import dotenv from "dotenv";
import path from "path";
import { PerformanceObserver, PerformanceObserverEntryList, PerformanceEntry } from "perf_hooks";
import {
PerformanceObserver,
PerformanceObserverEntryList,
PerformanceEntry,
} from "perf_hooks";
import express, { Express, NextFunction, Request, Response } from "express";
import session from "express-session";
import RedisStore from "connect-redis"
import RedisStore from "connect-redis";
import { createClient, RedisClientType } from "redis";
import { AccountInfo, AuthorizationCodeRequest } from "@azure/msal-node";

import { AppConfig, AuthProvider } from "./AuthProvider";
import { auth } from "./middleware";
import AxiosHelper from "./AxiosHelper";

declare module 'express-session' {
declare module "express-session" {
interface SessionData {
id: string;
isAuthenticated: boolean;
account: AccountInfo;
tokenRequest: AuthorizationCodeRequest;
protectedResources: Record<string, {
accessToken: string;
callingRoute: string;
}>;
protectedResources: Record<
string,
{
accessToken: string;
callingRoute: string;
}
>;
}
}

Expand All @@ -52,17 +59,29 @@ async function main() {
* Using express-session middleware for persistent user session. Be sure to
* familiarize yourself with available options. Visit: https://www.npmjs.com/package/express-session
*/
app.use(session({
store: new RedisStore({ client: cacheClient }),
// Production apps should use certificates, not secrets.
secret: process.env.CLIENT_SECRET || "ENTER_SESSION_SECRET_HERE",
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: false, // set this to true on production
}
}));
app.use(
session({
store: new RedisStore({ client: cacheClient }),
// Production apps should use certificates, not secrets.
secret: process.env.CLIENT_SECRET || "ENTER_SESSION_SECRET_HERE",
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: false, // set this to true on production

// Since token caching is user based, not session based,
// the user should be redirected to login from time to time so that
// the issuer can re-asses the state of the device.
// This will mostly be silent because the token issuer (Microsoft Entra) has its own cookies.
//
// If no expiration is set, clients will consider this a non-persistent cookie
// and usually delete it when the browser is closed.
//
maxAge: 60 * 60 * 1000, // 1h
},
})
);

// view engine setup
app.set("views", path.join(__dirname, "views"));
Expand All @@ -76,44 +95,68 @@ async function main() {
* Custom middleware to handle authentication and authorization
* using express session and MSAL Node CCA acquireToken* APIs.
*/
app.use(auth({
appConfig,
authProvider,
protectedResources: {
"/call-graph-direct": ["https://graph.microsoft.com/v1.0/me", {
scopes: ["User.Read"],
}],
"/call-graph-on-behalf": ["http://localhost:5000/obo", {
scopes: ["api://ENTER_API_CLIENT_ID_HERE/access_as_user"],
}],
app.use(
auth({
appConfig,
authProvider,
protectedResources: {
"/call-graph-direct": [
"https://graph.microsoft.com/v1.0/me",
{
scopes: ["User.Read"],
},
],
"/call-graph-on-behalf": [
"http://localhost:5000/obo",
{
scopes: [
"api://ENTER_API_CLIENT_ID_HERE/access_as_user",
],
},
],
},
})
);

app.get(
"/call-graph-direct",
async (req: Request, res: Response, next: NextFunction) => {
try {
const graphResponse = await AxiosHelper.callDownstreamApi(
"https://graph.microsoft.com/v1.0/me",
req.session.protectedResources
? req.session.protectedResources[
"https://graph.microsoft.com/v1.0/me"
].accessToken
: undefined
);

res.render("profile", { profile: graphResponse });
} catch (error) {
next(error);
}
}
}));

app.get("/call-graph-direct", async (req: Request, res: Response, next: NextFunction) => {
try {
const graphResponse = await AxiosHelper.callDownstreamApi(
"https://graph.microsoft.com/v1.0/me",
req.session.protectedResources ? req.session.protectedResources["https://graph.microsoft.com/v1.0/me"].accessToken : undefined
);

res.render("profile", { profile: graphResponse })
} catch (error) {
next(error);
);

app.get(
"/call-graph-on-behalf",
async (req: Request, res: Response, next: NextFunction) => {
try {
const graphResponse = await AxiosHelper.callDownstreamApi(
"http://localhost:5000/obo",
req.session.protectedResources
? req.session.protectedResources[
"http://localhost:5000/obo"
].accessToken
: undefined
);

res.render("profile", { profile: graphResponse });
} catch (error) {
next(error);
}
}
});

app.get("/call-graph-on-behalf", async (req: Request, res: Response, next: NextFunction) => {
try {
const graphResponse = await AxiosHelper.callDownstreamApi(
"http://localhost:5000/obo",
req.session.protectedResources ? req.session.protectedResources["http://localhost:5000/obo"].accessToken : undefined
);

res.render("profile", { profile: graphResponse })
} catch (error) {
next(error);
}
});
);

app.get("/", (req: Request, res: Response) => {
res.render("index", {
Expand All @@ -139,35 +182,41 @@ async function main() {
}

function initializePerformanceObserver(): void {
const perfObserver = new PerformanceObserver((items: PerformanceObserverEntryList) => {
let durationTotalInMs = 0;
let tokenSource;

items.getEntriesByName("acquireTokenByCode").forEach((entry: PerformanceEntry) => {
durationTotalInMs = entry.duration;
tokenSource = "network";
});

items.getEntriesByName("acquireTokenSilent").forEach((entry: PerformanceEntry) => {
durationTotalInMs = entry.duration;
tokenSource = "cache";
});

const results = {
tokenSource,
durationTotalInMs
};

fs.appendFile(
"benchmarks.json",
`${JSON.stringify(results)}\n`,
function (err) {
if (err) {
throw err;
const perfObserver = new PerformanceObserver(
(items: PerformanceObserverEntryList) => {
let durationTotalInMs = 0;
let tokenSource;

items
.getEntriesByName("acquireTokenByCode")
.forEach((entry: PerformanceEntry) => {
durationTotalInMs = entry.duration;
tokenSource = "network";
});

items
.getEntriesByName("acquireTokenSilent")
.forEach((entry: PerformanceEntry) => {
durationTotalInMs = entry.duration;
tokenSource = "cache";
});

const results = {
tokenSource,
durationTotalInMs,
};

fs.appendFile(
"benchmarks.json",
`${JSON.stringify(results)}\n`,
function (err) {
if (err) {
throw err;
}
}
}
);
});
);
}
);

perfObserver.observe({ entryTypes: ["measure"], buffered: true });
}
Expand All @@ -179,11 +228,11 @@ async function initializeRedisClient(): Promise<RedisClientType> {
*/
const redis = createClient({
socket: {
reconnectStrategy: false
}
reconnectStrategy: false,
},
});

redis.on('error', (err: any) => console.log('Redis Client Error', err));
redis.on("error", (err: any) => console.log("Redis Client Error", err));

await redis.connect();
return redis as RedisClientType;
Expand Down

0 comments on commit 65b8545

Please sign in to comment.