Skip to content

Commit

Permalink
Merge pull request #108 from alanszp/feature/add-express-jwt-cookie
Browse files Browse the repository at this point in the history
add logic to parse correctly cookies on req
  • Loading branch information
alanszp authored Aug 10, 2024
2 parents 686e778 + 736a673 commit 2a1ae40
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 15 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## v16.1.0

- Add `@alanszp/express`: `JwtCookiesKeys` enum to define common keys for JWT cookies
- Add `@alanszp/express`: `createAuthContext`/`tsoaAuthProvider` a way to override define cookies priority to get JWT token.
- Add `@alanszp/express`: Now it really parse the cookies from header using `cookie` lib. Before was using an empty object, so we really never used the req.cookies.jwt token.
- Add `@alanszp/express`: Export `parseCookie` form `cookie` lib.

## v16.0.3

- Add `@alanszp/jwt`: New methods to create permission strings easier. Principally `PermissionsService.getPermission` and `BitmaskUtils.encodePermissionsToBase64`
Expand Down
2 changes: 2 additions & 0 deletions packages/express/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
},
"devDependencies": {
"@types/body-parser": "^1.19.1",
"@types/cookie": "^0.6.0",
"@types/express": "^4.17.12",
"@types/jest": "^28.1.8",
"@types/joi": "^17.2.3",
Expand All @@ -46,6 +47,7 @@
"@babel/core": "^7.23.9",
"@paralleldrive/cuid2": "^2.2.2",
"body-parser": "^1.20.2",
"cookie": "^0.6.0",
"lodash": "^4.17.21",
"newrelic": "^11.18.0"
}
Expand Down
1 change: 1 addition & 0 deletions packages/express/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export * from "./middlewares/hasPermissions";
export * from "./helpers/getRequestBaseLog";
export * from "./endpoints/BaseApi";
export * from "./errors/AuthMethodFailureError";
export { parse as parseCookie } from "cookie";
41 changes: 29 additions & 12 deletions packages/express/src/middlewares/authenticateUser.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { JWTUser, verifyJWT, VerifyOptions } from "@alanszp/jwt";
import { NextFunction, Response } from "express";
import { parse as parseCookie } from "cookie";
import { getRequestLogger } from "../helpers/getRequestLogger";
import { GenericRequest } from "../types/GenericRequest";
import { ILogger } from "@alanszp/logger";
import { compact, isEmpty, omit } from "lodash";
import { compact, isEmpty, omit, reduce } from "lodash";
import { AuthenticationMethodError } from "../errors/AuthMethodFailureError";
import { render401Error } from "../helpers/renderErrorJson";

Expand All @@ -23,13 +24,20 @@ export enum AuthMethods {
API_KEY = "API_KEY",
}

export enum JwtCookiesKeys {
USER_ACCESS = "jwt",
IMPERSONATED_ACCESS = "impersonatedJwt",
PARTIAL_LOGIN_ACCESS = "partialAccessJwt",
}

export interface JWTVerifyOptions extends Partial<VerifyOptions> {
publicKey: string;
}

export interface JWTOptions {
jwtVerifyOptions: JWTVerifyOptions;
types: [AuthMethods.JWT];
cookiesKeys?: string[];
}

export interface ApiKeyOptions {
Expand All @@ -39,6 +47,7 @@ export interface ApiKeyOptions {

export interface BothMethodsOptions {
jwtVerifyOptions: JWTVerifyOptions;
cookiesKeys?: string[];
validApiKeys: string[];
types:
| [AuthMethods.JWT, AuthMethods.API_KEY]
Expand All @@ -50,17 +59,31 @@ export type AuthOptions = JWTOptions | ApiKeyOptions | BothMethodsOptions;
const middlewareGetterByAuthType: Record<
AuthMethods,
(
tokenOrJwt: string | null | undefined,
tokenOrJwt: GenericRequest,
options: AuthOptions,
logger: ILogger
) => Promise<JWTUser | null | undefined>
> = {
[AuthMethods.JWT]: async (
jwt: string | null | undefined,
req: GenericRequest,
options: Exclude<AuthOptions, ApiKeyOptions>,
logger: ILogger
) => {
try {
const cookiesKeys = options.cookiesKeys || [
JwtCookiesKeys.IMPERSONATED_ACCESS,
JwtCookiesKeys.USER_ACCESS,
];
const cookies = parseCookie(req.headers?.cookie ?? "");

const jwtFromCookies = reduce(
cookiesKeys,
(acc, key) => acc ?? cookies[key],
null
);
const jwt =
jwtFromCookies ?? parseAuthorizationHeader(req.headers.authorization);

if (!jwt) return undefined;
const jwtUser = await verifyJWT(
options.jwtVerifyOptions.publicKey,
Expand All @@ -78,10 +101,11 @@ const middlewareGetterByAuthType: Record<
}
},
[AuthMethods.API_KEY]: async (
token: string | null | undefined,
req: GenericRequest,
options: Exclude<AuthOptions, JWTOptions>,
logger: ILogger
): Promise<JWTUser | null | undefined> => {
const token = req.headers?.authorization;
try {
if (!token) return undefined;
if (options.validApiKeys.includes(token)) {
Expand Down Expand Up @@ -149,18 +173,11 @@ export async function tsoaAuthProvider<Options extends AuthOptions>(
authMethods: AuthMethods[]
): Promise<JWTUser> {
const logger = getRequestLogger(req);
const cookies = (req.cookies as Record<string, string | undefined>) || {};
const jwt =
cookies.jwt || parseAuthorizationHeader(req.headers.authorization);

try {
const authAttempts = await Promise.all(
authMethods.map((method) =>
middlewareGetterByAuthType[method](
method === AuthMethods.JWT ? jwt : req.headers.authorization,
options,
logger
)
middlewareGetterByAuthType[method](req, options, logger)
)
);

Expand Down
23 changes: 20 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2991,6 +2991,11 @@
resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.8.tgz#6742a5971f490dc41e59d277eee71361fea0b537"
integrity sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==

"@types/cookie@^0.6.0":
version "0.6.0"
resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5"
integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==

"@types/cookies@*":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@types/cookies/-/cookies-0.9.0.tgz#a2290cfb325f75f0f28720939bee854d4142aee2"
Expand Down Expand Up @@ -4332,9 +4337,9 @@ [email protected]:
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==

[email protected]:
[email protected], cookie@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
resolved "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==

core-js-compat@^3.31.0, core-js-compat@^3.34.0:
Expand Down Expand Up @@ -9252,7 +9257,19 @@ tar-stream@^2.1.4, tar-stream@~2.2.0:
inherits "^2.0.3"
readable-stream "^3.1.1"

[email protected], tar@^6.1.11, tar@^6.1.2:
[email protected]:
version "6.1.11"
resolved "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
dependencies:
chownr "^2.0.0"
fs-minipass "^2.0.0"
minipass "^3.0.0"
minizlib "^2.1.1"
mkdirp "^1.0.3"
yallist "^4.0.0"

tar@^6.1.11, tar@^6.1.2:
version "6.2.1"
resolved "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a"
integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==
Expand Down

0 comments on commit 2a1ae40

Please sign in to comment.