Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add express session maxAge #6554

Merged
merged 1 commit into from
Oct 23, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only actual change is at line 81, the rest is linting.

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
Loading