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

feat: get cookie name #957

Merged
merged 4 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

## [21.1.0] - 2024-11-19

- Adds `getCookieNameForTokenType` config option to allow customizing the cookie name for a token type.
- Adds `getResponseHeaderNameForTokenType` config option to allow customizing the response header name for a token type.
- Please note, that using this will require further customizations on the frontend

## [21.0.0] - 2024-10-07

- Added OAuth2Provider recipe
Expand Down
8 changes: 8 additions & 0 deletions lib/build/recipe/session/constants.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,11 @@ export declare const availableTokenTransferMethods: TokenTransferMethod[];
export declare const oneYearInMs = 31536000000;
export declare const JWKCacheCooldownInMs = 500;
export declare const protectedProps: string[];
export declare const authorizationHeaderKey = "authorization";
export declare const accessTokenHeaderKey = "st-access-token";
export declare const accessTokenCookieKey = "sAccessToken";
export declare const refreshTokenCookieKey = "sRefreshToken";
export declare const refreshTokenHeaderKey = "st-refresh-token";
export declare const antiCsrfHeaderKey = "anti-csrf";
export declare const frontTokenHeaderKey = "front-token";
export declare const authModeHeaderKey = "st-auth-mode";
10 changes: 9 additions & 1 deletion lib/build/recipe/session/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.protectedProps = exports.JWKCacheCooldownInMs = exports.oneYearInMs = exports.availableTokenTransferMethods = exports.SIGNOUT_API_PATH = exports.REFRESH_API_PATH = void 0;
exports.authModeHeaderKey = exports.frontTokenHeaderKey = exports.antiCsrfHeaderKey = exports.refreshTokenHeaderKey = exports.refreshTokenCookieKey = exports.accessTokenCookieKey = exports.accessTokenHeaderKey = exports.authorizationHeaderKey = exports.protectedProps = exports.JWKCacheCooldownInMs = exports.oneYearInMs = exports.availableTokenTransferMethods = exports.SIGNOUT_API_PATH = exports.REFRESH_API_PATH = void 0;
exports.REFRESH_API_PATH = "/session/refresh";
exports.SIGNOUT_API_PATH = "/signout";
exports.availableTokenTransferMethods = ["cookie", "header"];
Expand All @@ -32,3 +32,11 @@ exports.protectedProps = [
"tId",
"stt",
];
exports.authorizationHeaderKey = "authorization";
exports.accessTokenHeaderKey = "st-access-token";
exports.accessTokenCookieKey = "sAccessToken";
exports.refreshTokenCookieKey = "sRefreshToken";
exports.refreshTokenHeaderKey = "st-refresh-token";
exports.antiCsrfHeaderKey = "anti-csrf";
exports.frontTokenHeaderKey = "front-token";
exports.authModeHeaderKey = "st-auth-mode";
19 changes: 12 additions & 7 deletions lib/build/recipe/session/cookieAndHeaders.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,27 @@ import { TokenTransferMethod, TokenType, TypeNormalisedInput } from "./types";
export declare function clearSessionFromAllTokenTransferMethods(
config: TypeNormalisedInput,
res: BaseResponse,
request: BaseRequest | undefined,
request: BaseRequest,
userContext: UserContext
): void;
export declare function clearSession(
config: TypeNormalisedInput,
res: BaseResponse,
transferMethod: TokenTransferMethod,
request: BaseRequest | undefined,
request: BaseRequest,
userContext: UserContext
): void;
export declare function getAntiCsrfTokenFromHeaders(req: BaseRequest): string | undefined;
export declare function setAntiCsrfTokenInHeaders(res: BaseResponse, antiCsrfToken: string): void;
export declare function buildFrontToken(userId: string, atExpiry: number, accessTokenPayload: any): string;
export declare function setFrontTokenInHeaders(res: BaseResponse, frontToken: string): void;
export declare function getCORSAllowedHeaders(): string[];
export declare function getCookieNameFromTokenType(tokenType: TokenType): "sAccessToken" | "sRefreshToken";
export declare function getResponseHeaderNameForTokenType(tokenType: TokenType): "st-access-token" | "st-refresh-token";
export declare function getToken(
config: TypeNormalisedInput,
req: BaseRequest,
tokenType: TokenType,
transferMethod: TokenTransferMethod
transferMethod: TokenTransferMethod,
userContext: UserContext
): string | undefined;
export declare function setToken(
config: TypeNormalisedInput,
Expand All @@ -34,7 +34,7 @@ export declare function setToken(
value: string,
expires: number,
transferMethod: TokenTransferMethod,
req: BaseRequest | undefined,
req: BaseRequest,
userContext: UserContext
): void;
export declare function setHeader(res: BaseResponse, name: string, value: string): void;
Expand Down Expand Up @@ -83,4 +83,9 @@ export declare function clearSessionCookiesFromOlderCookieDomain({
config: TypeNormalisedInput;
userContext: UserContext;
}): void;
export declare function hasMultipleCookiesForTokenType(req: BaseRequest, tokenType: TokenType): boolean;
export declare function hasMultipleCookiesForTokenType(
config: TypeNormalisedInput,
req: BaseRequest,
tokenType: TokenType,
userContext: UserContext
): boolean;
75 changes: 26 additions & 49 deletions lib/build/recipe/session/cookieAndHeaders.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ var __importDefault =
return mod && mod.__esModule ? mod : { default: mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasMultipleCookiesForTokenType = exports.clearSessionCookiesFromOlderCookieDomain = exports.getAuthModeFromHeader = exports.setCookie = exports.setHeader = exports.setToken = exports.getToken = exports.getResponseHeaderNameForTokenType = exports.getCookieNameFromTokenType = exports.getCORSAllowedHeaders = exports.setFrontTokenInHeaders = exports.buildFrontToken = exports.setAntiCsrfTokenInHeaders = exports.getAntiCsrfTokenFromHeaders = exports.clearSession = exports.clearSessionFromAllTokenTransferMethods = void 0;
exports.hasMultipleCookiesForTokenType = exports.clearSessionCookiesFromOlderCookieDomain = exports.getAuthModeFromHeader = exports.setCookie = exports.setHeader = exports.setToken = exports.getToken = exports.getCORSAllowedHeaders = exports.setFrontTokenInHeaders = exports.buildFrontToken = exports.setAntiCsrfTokenInHeaders = exports.getAntiCsrfTokenFromHeaders = exports.clearSession = exports.clearSessionFromAllTokenTransferMethods = void 0;
/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved.
*
* This software is licensed under the Apache License, Version 2.0 (the
Expand All @@ -25,14 +25,6 @@ const logger_1 = require("../../logger");
const utils_1 = require("../../utils");
const constants_2 = require("./constants");
const error_1 = __importDefault(require("./error"));
const authorizationHeaderKey = "authorization";
const accessTokenCookieKey = "sAccessToken";
const accessTokenHeaderKey = "st-access-token";
const refreshTokenCookieKey = "sRefreshToken";
const refreshTokenHeaderKey = "st-refresh-token";
const antiCsrfHeaderKey = "anti-csrf";
const frontTokenHeaderKey = "front-token";
const authModeHeaderKey = "st-auth-mode";
function clearSessionFromAllTokenTransferMethods(config, res, request, userContext) {
// We are clearing the session in all transfermethods to be sure to override cookies in case they have been already added to the response.
// This is done to handle the following use-case:
Expand All @@ -51,19 +43,19 @@ function clearSession(config, res, transferMethod, request, userContext) {
for (const token of tokenTypes) {
setToken(config, res, token, "", 0, transferMethod, request, userContext);
}
res.removeHeader(antiCsrfHeaderKey);
res.removeHeader(constants_2.antiCsrfHeaderKey);
// This can be added multiple times in some cases, but that should be OK
res.setHeader(frontTokenHeaderKey, "remove", false);
res.setHeader("Access-Control-Expose-Headers", frontTokenHeaderKey, true);
res.setHeader(constants_2.frontTokenHeaderKey, "remove", false);
res.setHeader("Access-Control-Expose-Headers", constants_2.frontTokenHeaderKey, true);
}
exports.clearSession = clearSession;
function getAntiCsrfTokenFromHeaders(req) {
return req.getHeaderValue(antiCsrfHeaderKey);
return req.getHeaderValue(constants_2.antiCsrfHeaderKey);
}
exports.getAntiCsrfTokenFromHeaders = getAntiCsrfTokenFromHeaders;
function setAntiCsrfTokenInHeaders(res, antiCsrfToken) {
res.setHeader(antiCsrfHeaderKey, antiCsrfToken, false);
res.setHeader("Access-Control-Expose-Headers", antiCsrfHeaderKey, true);
res.setHeader(constants_2.antiCsrfHeaderKey, antiCsrfToken, false);
res.setHeader("Access-Control-Expose-Headers", constants_2.antiCsrfHeaderKey, true);
}
exports.setAntiCsrfTokenInHeaders = setAntiCsrfTokenInHeaders;
function buildFrontToken(userId, atExpiry, accessTokenPayload) {
Expand All @@ -76,41 +68,24 @@ function buildFrontToken(userId, atExpiry, accessTokenPayload) {
}
exports.buildFrontToken = buildFrontToken;
function setFrontTokenInHeaders(res, frontToken) {
res.setHeader(frontTokenHeaderKey, frontToken, false);
res.setHeader("Access-Control-Expose-Headers", frontTokenHeaderKey, true);
res.setHeader(constants_2.frontTokenHeaderKey, frontToken, false);
res.setHeader("Access-Control-Expose-Headers", constants_2.frontTokenHeaderKey, true);
}
exports.setFrontTokenInHeaders = setFrontTokenInHeaders;
function getCORSAllowedHeaders() {
return [antiCsrfHeaderKey, constants_1.HEADER_RID, authorizationHeaderKey, authModeHeaderKey];
return [
constants_2.antiCsrfHeaderKey,
constants_1.HEADER_RID,
constants_2.authorizationHeaderKey,
constants_2.authModeHeaderKey,
];
}
exports.getCORSAllowedHeaders = getCORSAllowedHeaders;
function getCookieNameFromTokenType(tokenType) {
switch (tokenType) {
case "access":
return accessTokenCookieKey;
case "refresh":
return refreshTokenCookieKey;
default:
throw new Error("Unknown token type, should never happen.");
}
}
exports.getCookieNameFromTokenType = getCookieNameFromTokenType;
function getResponseHeaderNameForTokenType(tokenType) {
switch (tokenType) {
case "access":
return accessTokenHeaderKey;
case "refresh":
return refreshTokenHeaderKey;
default:
throw new Error("Unknown token type, should never happen.");
}
}
exports.getResponseHeaderNameForTokenType = getResponseHeaderNameForTokenType;
function getToken(req, tokenType, transferMethod) {
function getToken(config, req, tokenType, transferMethod, userContext) {
if (transferMethod === "cookie") {
return req.getCookieValue(getCookieNameFromTokenType(tokenType));
return req.getCookieValue(config.getCookieNameForTokenType(req, tokenType, userContext));
} else if (transferMethod === "header") {
const value = req.getHeaderValue(authorizationHeaderKey);
const value = req.getHeaderValue(constants_2.authorizationHeaderKey);
if (value === undefined || !value.startsWith("Bearer ")) {
return undefined;
}
Expand All @@ -126,15 +101,15 @@ function setToken(config, res, tokenType, value, expires, transferMethod, req, u
setCookie(
config,
res,
getCookieNameFromTokenType(tokenType),
config.getCookieNameForTokenType(req, tokenType, userContext),
value,
expires,
tokenType === "refresh" ? "refreshTokenPath" : "accessTokenPath",
req,
userContext
);
} else if (transferMethod === "header") {
setHeader(res, getResponseHeaderNameForTokenType(tokenType), value);
setHeader(res, config.getResponseHeaderNameForTokenType(req, tokenType, userContext), value);
}
}
exports.setToken = setToken;
Expand Down Expand Up @@ -174,7 +149,9 @@ function setCookie(config, res, name, value, expires, pathType, req, userContext
exports.setCookie = setCookie;
function getAuthModeFromHeader(req) {
var _a;
return (_a = req.getHeaderValue(authModeHeaderKey)) === null || _a === void 0 ? void 0 : _a.toLowerCase();
return (_a = req.getHeaderValue(constants_2.authModeHeaderKey)) === null || _a === void 0
? void 0
: _a.toLowerCase();
}
exports.getAuthModeFromHeader = getAuthModeFromHeader;
/**
Expand Down Expand Up @@ -202,7 +179,7 @@ function clearSessionCookiesFromOlderCookieDomain({ req, res, config, userContex
let didClearCookies = false;
const tokenTypes = ["access", "refresh"];
for (const token of tokenTypes) {
if (hasMultipleCookiesForTokenType(req, token)) {
if (hasMultipleCookiesForTokenType(config, req, token, userContext)) {
// If a request has multiple session cookies and 'olderCookieDomain' is
// unset, we can't identify the correct cookie for refreshing the session.
// Using the wrong cookie can cause an infinite refresh loop. To avoid this,
Expand Down Expand Up @@ -237,13 +214,13 @@ function clearSessionCookiesFromOlderCookieDomain({ req, res, config, userContex
}
}
exports.clearSessionCookiesFromOlderCookieDomain = clearSessionCookiesFromOlderCookieDomain;
function hasMultipleCookiesForTokenType(req, tokenType) {
function hasMultipleCookiesForTokenType(config, req, tokenType, userContext) {
const cookieString = req.getHeaderValue("cookie");
if (cookieString === undefined) {
return false;
}
const cookies = parseCookieStringFromRequestHeaderAllowingDuplicates(cookieString);
const cookieName = getCookieNameFromTokenType(tokenType);
const cookieName = config.getCookieNameForTokenType(req, tokenType, userContext);
return cookies[cookieName] !== undefined && cookies[cookieName].length > 1;
}
exports.hasMultipleCookiesForTokenType = hasMultipleCookiesForTokenType;
Expand Down
4 changes: 3 additions & 1 deletion lib/build/recipe/session/sessionRequestFunctions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ export declare function getSessionFromRequest({
userContext: UserContext;
}): Promise<SessionContainerInterface | undefined>;
export declare function getAccessTokenFromRequest(
config: TypeNormalisedInput,
req: any,
allowedTransferMethod: TokenTransferMethod | "any"
allowedTransferMethod: TokenTransferMethod | "any",
userContext: UserContext
): {
requestTransferMethod: TokenTransferMethod | undefined;
accessToken: ParsedJWTInfo | undefined;
Expand Down
30 changes: 23 additions & 7 deletions lib/build/recipe/session/sessionRequestFunctions.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, op
forCreateNewSession: false,
userContext,
});
const { requestTransferMethod, accessToken } = getAccessTokenFromRequest(req, allowedTransferMethod);
const { requestTransferMethod, accessToken } = getAccessTokenFromRequest(
config,
req,
allowedTransferMethod,
userContext
);
let antiCsrfToken = cookieAndHeaders_1.getAntiCsrfTokenFromHeaders(req);
let doAntiCsrfCheck = options !== undefined ? options.antiCsrfCheck : undefined;
if (doAntiCsrfCheck === undefined) {
Expand Down Expand Up @@ -123,11 +128,11 @@ async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, op
return session;
}
exports.getSessionFromRequest = getSessionFromRequest;
function getAccessTokenFromRequest(req, allowedTransferMethod) {
function getAccessTokenFromRequest(config, req, allowedTransferMethod, userContext) {
const accessTokens = {};
// We check all token transfer methods for available access tokens
for (const transferMethod of constants_1.availableTokenTransferMethods) {
const tokenString = cookieAndHeaders_1.getToken(req, "access", transferMethod);
const tokenString = cookieAndHeaders_1.getToken(config, req, "access", transferMethod, userContext);
if (tokenString !== undefined) {
try {
const info = jwt_1.parseJWTWithoutSignatureVerification(tokenString);
Expand Down Expand Up @@ -158,7 +163,12 @@ function getAccessTokenFromRequest(req, allowedTransferMethod) {
// If multiple access tokens exist in the request cookie, throw TRY_REFRESH_TOKEN.
// This prompts the client to call the refresh endpoint, clearing olderCookieDomain cookies (if set).
// ensuring outdated token payload isn't used.
const hasMultipleAccessTokenCookies = cookieAndHeaders_1.hasMultipleCookiesForTokenType(req, "access");
const hasMultipleAccessTokenCookies = cookieAndHeaders_1.hasMultipleCookiesForTokenType(
config,
req,
"access",
userContext
);
if (hasMultipleAccessTokenCookies) {
logger_1.logDebugMessage(
"getSession: Throwing TRY_REFRESH_TOKEN because multiple access tokens are present in request cookies"
Expand Down Expand Up @@ -196,7 +206,13 @@ async function refreshSessionInRequest({ res, req, userContext, config, recipeIn
// We check all token transfer methods for available refresh tokens
// We do this so that we can later clear all we are not overwriting
for (const transferMethod of constants_1.availableTokenTransferMethods) {
refreshTokens[transferMethod] = cookieAndHeaders_1.getToken(req, "refresh", transferMethod);
refreshTokens[transferMethod] = cookieAndHeaders_1.getToken(
config,
req,
"refresh",
transferMethod,
userContext
);
if (refreshTokens[transferMethod] !== undefined) {
logger_1.logDebugMessage("refreshSession: got refresh token from " + transferMethod);
}
Expand Down Expand Up @@ -244,7 +260,7 @@ async function refreshSessionInRequest({ res, req, userContext, config, recipeIn
// See: https://github.com/supertokens/supertokens-node/issues/790
if (
(allowedTransferMethod === "any" || allowedTransferMethod === "cookie") &&
cookieAndHeaders_1.getToken(req, "access", "cookie") !== undefined
cookieAndHeaders_1.getToken(config, req, "access", "cookie", userContext) !== undefined
) {
logger_1.logDebugMessage(
"refreshSession: cleared all session tokens and returning UNAUTHORISED because refresh token in request is undefined"
Expand Down Expand Up @@ -446,7 +462,7 @@ async function createNewSessionInRequest({
for (const transferMethod of constants_1.availableTokenTransferMethods) {
if (
transferMethod !== outputTransferMethod &&
cookieAndHeaders_1.getToken(req, "access", transferMethod) !== undefined
cookieAndHeaders_1.getToken(config, req, "access", transferMethod, userContext) !== undefined
) {
cookieAndHeaders_1.clearSession(config, res, transferMethod, req, userContext);
}
Expand Down
4 changes: 4 additions & 0 deletions lib/build/recipe/session/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export declare type TypeInput = {
forCreateNewSession: boolean;
userContext: UserContext;
}) => TokenTransferMethod | "any";
getCookieNameForTokenType?: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string;
getResponseHeaderNameForTokenType?: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string;
errorHandlers?: ErrorHandlers;
antiCsrf?: "VIA_TOKEN" | "VIA_CUSTOM_HEADER" | "NONE";
exposeAccessTokenToFrontendInCookieBasedAuth?: boolean;
Expand All @@ -74,6 +76,8 @@ export declare type TypeNormalisedInput = {
userContext: UserContext;
}) => "strict" | "lax" | "none";
cookieSecure: boolean;
getCookieNameForTokenType: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string;
getResponseHeaderNameForTokenType: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string;
sessionExpiredStatusCode: number;
errorHandlers: NormalisedErrorHandlers;
antiCsrfFunctionOrString:
Expand Down
Loading
Loading