passport-entra 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # README #
2
+
3
+ This README would normally document whatever steps are necessary to get your application up and running.
4
+
5
+ ### What is this repository for? ###
6
+
7
+ * Quick summary
8
+ * Version
9
+ * [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo)
10
+
11
+ ### How do I get set up? ###
12
+
13
+ * Summary of set up
14
+ * Configuration
15
+ * Dependencies
16
+ * Database configuration
17
+ * How to run tests
18
+ * Deployment instructions
19
+
20
+ ### Contribution guidelines ###
21
+
22
+ * Writing tests
23
+ * Code review
24
+ * Other guidelines
25
+
26
+ ### Who do I talk to? ###
27
+
28
+ * Repo owner or admin
29
+ * Other community or team contact
@@ -1,39 +1,52 @@
1
1
  import { Strategy } from 'passport';
2
+ import type { AuthenticateCallback } from 'passport';
2
3
  import { createRemoteJWKSet } from 'jose';
3
4
  import type { Request } from 'express';
4
- export interface OauthBearerOptions {
5
+ /**
6
+ * Bearerstrategy options
7
+ */
8
+ export interface BearerOptions {
9
+ /** If provided, check aud claim against this audience, otherwise use clientID. */
5
10
  audience?: string | string[];
11
+ /** ClientID of app in Entra, check aud claim against clientID if audience is not provided. */
6
12
  clientID: string;
13
+ /** Clock skew allowed when checking nbf and exp claims. */
7
14
  clockSkew?: string | number;
15
+ /** Well known oid-configuration endpoint, i.e. _issuer_/.well-known/openid-configuration. */
8
16
  identityMetadata: string;
17
+ /** Check iss claim, i.e. https://login.microsoftonline.com/_tenantID_/v2.0 - tenantID of app in Entra */
9
18
  issuer: string | string[];
19
+ /** Check scp claim. */
10
20
  scope?: string[];
11
21
  }
12
- type VerifyCallback = (token: object, done: AuthenticateCallback) => void;
13
- export type AuthenticateCallback = (err: unknown, user?: Express.User | false | null, info?: object, status?: number | number[]) => void;
14
22
  /**
15
- * Bearerstrategy for password.js, used with Microsoft Entra as identity provider
23
+ * Called by this BearerStrategy when the token has been validated.
24
+ * Use the token (the bearer JWT payload) to find the user. Then call done.
25
+ */
26
+ export type VerifyCallback = (token: object, done: AuthenticateCallback) => void;
27
+ /**
28
+ * Bearerstrategy for password.js, used with Microsoft Entra as identity provider.
16
29
  */
17
30
  export default class BearerStrategy extends Strategy {
18
- /** aud claim check */
19
31
  audience: string[];
20
- /** clientID to check */
21
32
  clientID: string;
22
- /** clockSkew allowed when checking nbf and exp */
23
33
  clockSkew: string | number;
24
- /** well known oid-configuration endpoint */
25
34
  identityMetadata: string;
26
- /** iss claim check */
27
35
  issuer: string | string[];
28
- /** strategy name */
29
36
  name: string;
30
- /** scp claim check */
31
37
  scope: string[];
32
- /** call this function to get the user belonging to the token */
33
38
  verifyFn: VerifyCallback;
34
- /** cached JWK set from well known oid-configuration endpoint */
35
39
  jwks: ReturnType<typeof createRemoteJWKSet> | undefined;
36
- constructor(options: OauthBearerOptions, verifyFn: VerifyCallback);
40
+ /**
41
+ * Creates a new BearerStrategy instance.
42
+ * @param options The validation options.
43
+ * @param verifyFn Called when a token is validated, needs to provide the User object.
44
+ */
45
+ constructor(options: BearerOptions, verifyFn: VerifyCallback);
46
+ /**
47
+ * Validates the Bearer token in a request.
48
+ * When validated, calls VerifyFn to retrieve the User belonging to the token.
49
+ * @param req The Express request
50
+ */
37
51
  authenticate(req: Request): Promise<void>;
38
52
  }
39
- export {};
@@ -1,82 +1,86 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  const passport_1 = require("passport");
13
4
  const jose_1 = require("jose");
14
5
  /**
15
- * Bearerstrategy for password.js, used with Microsoft Entra as identity provider
6
+ * Bearerstrategy for password.js, used with Microsoft Entra as identity provider.
16
7
  */
17
8
  class BearerStrategy extends passport_1.Strategy {
9
+ audience;
10
+ clientID;
11
+ clockSkew;
12
+ identityMetadata;
13
+ issuer;
14
+ name = 'oauth-bearer';
15
+ scope;
16
+ verifyFn;
17
+ jwks;
18
+ /**
19
+ * Creates a new BearerStrategy instance.
20
+ * @param options The validation options.
21
+ * @param verifyFn Called when a token is validated, needs to provide the User object.
22
+ */
18
23
  constructor(options, verifyFn) {
19
- var _a, _b, _c;
20
24
  super();
21
- /** strategy name */
22
- this.name = 'oauth-bearer';
23
25
  this.verifyFn = verifyFn;
24
- let audience = (_a = options.audience) !== null && _a !== void 0 ? _a : options.clientID;
26
+ let audience = options.audience ?? options.clientID;
25
27
  if (typeof audience === 'string') {
26
28
  audience = [audience, `spn:${audience}`];
27
29
  }
28
30
  this.audience = audience;
29
31
  this.clientID = options.clientID;
30
- this.clockSkew = (_b = options.clockSkew) !== null && _b !== void 0 ? _b : 300;
32
+ this.clockSkew = options.clockSkew ?? 300;
31
33
  this.identityMetadata = options.identityMetadata;
32
34
  this.issuer = options.issuer;
33
- this.scope = (_c = options.scope) !== null && _c !== void 0 ? _c : [];
35
+ this.scope = options.scope ?? [];
34
36
  }
35
- authenticate(req) {
36
- return __awaiter(this, void 0, void 0, function* () {
37
- var _a;
38
- try {
39
- if (!this.jwks) {
40
- const res = yield fetch(this.identityMetadata);
41
- if (!res.ok) {
42
- throw new Error(`Error ${String(res.status)} when querying identity metadata`);
43
- }
44
- // eslint-disable-next-line @typescript-eslint/naming-convention
45
- const { jwks_uri } = yield res.json();
46
- this.jwks = (0, jose_1.createRemoteJWKSet)(new URL(jwks_uri));
37
+ /**
38
+ * Validates the Bearer token in a request.
39
+ * When validated, calls VerifyFn to retrieve the User belonging to the token.
40
+ * @param req The Express request
41
+ */
42
+ async authenticate(req) {
43
+ try {
44
+ if (!this.jwks) {
45
+ const res = await fetch(this.identityMetadata);
46
+ if (!res.ok) {
47
+ throw new Error(`Error ${String(res.status)} when querying identity metadata`);
47
48
  }
48
- const header = req.get('authorization');
49
- const headerParts = header === null || header === void 0 ? void 0 : header.split(' ');
50
- const jwt = (headerParts === null || headerParts === void 0 ? void 0 : headerParts.length) === 2 && headerParts[0].toLowerCase() === 'bearer' && headerParts[1];
51
- if (!jwt) {
52
- throw new Error('Unable to extract Bearer token from Authorization header');
53
- }
54
- const { payload } = yield (0, jose_1.jwtVerify)(jwt, this.jwks, {
55
- audience: this.audience, // aud
56
- clockTolerance: this.clockSkew, // exp, nbf
57
- issuer: this.issuer, // iss
58
- });
59
- // console.log(JSON.stringify(payload, null, 2));
60
- const scp = String((_a = payload.scp) !== null && _a !== void 0 ? _a : ''); // scp
61
- if (this.scope.length > 0
62
- && !this.scope.some((scope) => scp.includes(scope))) {
63
- throw new Error('Scope does not match scp claim');
64
- }
65
- this.verifyFn(payload, (err, user, info) => {
66
- if (err) {
67
- throw err;
68
- }
69
- if (!user) {
70
- throw new Error('No user found');
71
- }
72
- this.success(user, info);
73
- });
49
+ // eslint-disable-next-line @typescript-eslint/naming-convention
50
+ const { jwks_uri } = await res.json();
51
+ this.jwks = (0, jose_1.createRemoteJWKSet)(new URL(jwks_uri));
74
52
  }
75
- catch (error) {
76
- const message = JSON.stringify(error);
77
- this.fail(message);
53
+ const header = req.get('authorization');
54
+ const headerParts = header?.split(' ');
55
+ const jwt = headerParts?.length === 2 && headerParts[0]?.toLowerCase() === 'bearer' && headerParts[1];
56
+ if (!jwt) {
57
+ throw new Error('Unable to extract Bearer token from Authorization header');
78
58
  }
79
- });
59
+ const { payload } = await (0, jose_1.jwtVerify)(jwt, this.jwks, {
60
+ audience: this.audience, // aud
61
+ clockTolerance: this.clockSkew, // exp, nbf
62
+ issuer: this.issuer, // iss
63
+ });
64
+ // console.log(JSON.stringify(payload, null, 2));
65
+ const scp = String(payload['scp'] ?? ''); // scp
66
+ if (this.scope.length > 0
67
+ && !this.scope.some((scope) => scp.includes(scope))) {
68
+ throw new Error('Scope does not match scp claim');
69
+ }
70
+ this.verifyFn(payload, (err, user, info) => {
71
+ if (err) {
72
+ throw err;
73
+ }
74
+ if (!user) {
75
+ throw new Error('No user found');
76
+ }
77
+ this.success(user, info);
78
+ });
79
+ }
80
+ catch (error) {
81
+ const message = JSON.stringify(error);
82
+ this.fail(message);
83
+ }
80
84
  }
81
85
  }
82
86
  exports.default = BearerStrategy;
package/package.json CHANGED
@@ -1,20 +1,25 @@
1
- {
2
- "name": "passport-entra",
3
- "version": "0.0.2",
4
- "description": "Microft Entra authentication strategy for passport",
5
- "main": "dist/passport-entra.js",
6
- "types": "dist/passport-entra.d.ts",
7
- "scripts": {
8
- "test": "echo \"Error: no test specified\" && exit 1"
9
- },
10
- "author": "Jan Bakker",
11
- "license": "ISC",
12
- "dependencies": {
13
- "jose": "^6.0.11",
14
- "passport": "^0.7.0",
15
- "typescript": "^5.8.3"
16
- },
17
- "devDependencies": {
18
- "@types/passport": "^1.0.17"
19
- }
20
- }
1
+ {
2
+ "name": "passport-entra",
3
+ "version": "0.0.4",
4
+ "description": "Microsoft Entra authentication strategy for passport",
5
+ "main": "dist/passport-entra.js",
6
+ "types": "dist/passport-entra.d.ts",
7
+ "homepage": "https://bitbucket.org/janbakker/passport-entra",
8
+ "repository": "git+https://bitbucket.org/janbakker/passport-entra.git",
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "author": "Jan Bakker",
14
+ "license": "ISC",
15
+ "dependencies": {
16
+ "jose": "^6.0.11",
17
+ "passport": "^0.7.0"
18
+ },
19
+ "devDependencies": {
20
+ "@tsconfig/node20": "^20.1.6",
21
+ "@tsconfig/strictest": "^2.0.5",
22
+ "@types/passport": "^1.0.17",
23
+ "typescript": "^5.8.3"
24
+ }
25
+ }
package/passport-entra.ts CHANGED
@@ -1,117 +1,120 @@
1
- import { Strategy } from 'passport';
2
- import { createRemoteJWKSet, jwtVerify } from 'jose';
3
-
4
- import type { Request } from 'express';
5
-
6
- export interface OauthBearerOptions {
7
- audience?: string | string[];
8
- clientID: string;
9
- clockSkew?: string | number;
10
- identityMetadata: string;
11
- issuer: string | string[];
12
- scope?: string[];
13
- }
14
-
15
- type VerifyCallback = (token: object, done: AuthenticateCallback) => void;
16
-
17
- export type AuthenticateCallback = (
18
- err: unknown,
19
- user?: Express.User | false | null,
20
- info?: object,
21
- status?: number | number[]
22
- ) => void;
23
-
24
- /**
25
- * Bearerstrategy for password.js, used with Microsoft Entra as identity provider
26
- */
27
- export default class BearerStrategy extends Strategy {
28
- /** aud claim check */
29
- audience;
30
-
31
- /** clientID to check */
32
- clientID;
33
-
34
- /** clockSkew allowed when checking nbf and exp */
35
- clockSkew;
36
-
37
- /** well known oid-configuration endpoint */
38
- identityMetadata: string;
39
-
40
- /** iss claim check */
41
- issuer;
42
-
43
- /** strategy name */
44
- name = 'oauth-bearer';
45
-
46
- /** scp claim check */
47
- scope;
48
-
49
- /** call this function to get the user belonging to the token */
50
- verifyFn;
51
-
52
- /** cached JWK set from well known oid-configuration endpoint */
53
- jwks: ReturnType<typeof createRemoteJWKSet> | undefined;
54
-
55
- constructor(options: OauthBearerOptions, verifyFn: VerifyCallback) {
56
- super();
57
-
58
- this.verifyFn = verifyFn;
59
-
60
- let audience = options.audience ?? options.clientID;
61
- if (typeof audience === 'string') {
62
- audience = [audience, `spn:${audience}`];
63
- }
64
- this.audience = audience;
65
-
66
- this.clientID = options.clientID;
67
- this.clockSkew = options.clockSkew ?? 300;
68
- this.identityMetadata = options.identityMetadata;
69
- this.issuer = options.issuer;
70
- this.scope = options.scope ?? [];
71
- }
72
-
73
- async authenticate(req: Request) {
74
- try {
75
- if (!this.jwks) {
76
- const res = await fetch(this.identityMetadata);
77
- if (!res.ok) {
78
- throw new Error(`Error ${String(res.status)} when querying identity metadata`);
79
- }
80
- // eslint-disable-next-line @typescript-eslint/naming-convention
81
- const { jwks_uri } = await res.json() as { jwks_uri: string };
82
- this.jwks = createRemoteJWKSet(new URL(jwks_uri));
83
- }
84
- const header = req.get('authorization');
85
- const headerParts = header?.split(' ');
86
- const jwt = headerParts?.length === 2 && headerParts[0].toLowerCase() === 'bearer' && headerParts[1];
87
- if (!jwt) {
88
- throw new Error('Unable to extract Bearer token from Authorization header');
89
- }
90
- const { payload } = await jwtVerify(jwt, this.jwks, {
91
- audience: this.audience, // aud
92
- clockTolerance: this.clockSkew, // exp, nbf
93
- issuer: this.issuer, // iss
94
- });
95
- // console.log(JSON.stringify(payload, null, 2));
96
-
97
- const scp = String(payload.scp ?? ''); // scp
98
- if (this.scope.length > 0
99
- && !this.scope.some((scope) => scp.includes(scope))) {
100
- throw new Error('Scope does not match scp claim');
101
- }
102
-
103
- this.verifyFn(payload, (err: unknown, user: unknown, info?: object) => {
104
- if (err) {
105
- throw err as Error;
106
- }
107
- if (!user) {
108
- throw new Error('No user found');
109
- }
110
- this.success(user, info);
111
- });
112
- } catch (error) {
113
- const message = JSON.stringify(error);
114
- this.fail(message);
115
- }
116
- }
117
- }
1
+ import { Strategy } from 'passport';
2
+ import type { AuthenticateCallback } from 'passport';
3
+
4
+ import { createRemoteJWKSet, jwtVerify } from 'jose';
5
+
6
+ import type { Request } from 'express';
7
+
8
+ /**
9
+ * Bearerstrategy options
10
+ */
11
+ export interface BearerOptions {
12
+ /** If provided, check aud claim against this audience, otherwise use clientID. */
13
+ audience?: string | string[];
14
+ /** ClientID of app in Entra, check aud claim against clientID if audience is not provided. */
15
+ clientID: string;
16
+ /** Clock skew allowed when checking nbf and exp claims. */
17
+ clockSkew?: string | number;
18
+ /** Well known oid-configuration endpoint, i.e. _issuer_/.well-known/openid-configuration. */
19
+ identityMetadata: string;
20
+ /** Check iss claim, i.e. https://login.microsoftonline.com/_tenantID_/v2.0 - tenantID of app in Entra */
21
+ issuer: string | string[];
22
+ /** Check scp claim. */
23
+ scope?: string[];
24
+ }
25
+
26
+ /**
27
+ * Called by this BearerStrategy when the token has been validated.
28
+ * Use the token (the bearer JWT payload) to find the user. Then call done.
29
+ */
30
+ export type VerifyCallback = (token: object, done: AuthenticateCallback) => void;
31
+
32
+ /**
33
+ * Bearerstrategy for password.js, used with Microsoft Entra as identity provider.
34
+ */
35
+ export default class BearerStrategy extends Strategy {
36
+ audience;
37
+ clientID;
38
+ clockSkew;
39
+ identityMetadata;
40
+ issuer;
41
+ override name = 'oauth-bearer';
42
+ scope;
43
+ verifyFn;
44
+ jwks: ReturnType<typeof createRemoteJWKSet> | undefined;
45
+
46
+ /**
47
+ * Creates a new BearerStrategy instance.
48
+ * @param options The validation options.
49
+ * @param verifyFn Called when a token is validated, needs to provide the User object.
50
+ */
51
+ constructor(options: BearerOptions, verifyFn: VerifyCallback) {
52
+ super();
53
+
54
+ this.verifyFn = verifyFn;
55
+
56
+ let audience = options.audience ?? options.clientID;
57
+ if (typeof audience === 'string') {
58
+ audience = [audience, `spn:${audience}`];
59
+ }
60
+ this.audience = audience;
61
+
62
+ this.clientID = options.clientID;
63
+ this.clockSkew = options.clockSkew ?? 300;
64
+ this.identityMetadata = options.identityMetadata;
65
+ this.issuer = options.issuer;
66
+ this.scope = options.scope ?? [];
67
+ }
68
+
69
+ /**
70
+ * Validates the Bearer token in a request.
71
+ * When validated, calls VerifyFn to retrieve the User belonging to the token.
72
+ * @param req The Express request
73
+ */
74
+ override async authenticate(req: Request) {
75
+ try {
76
+ if (!this.jwks) {
77
+ const res = await fetch(this.identityMetadata);
78
+ if (!res.ok) {
79
+ throw new Error(`Error ${String(res.status)} when querying identity metadata`);
80
+ }
81
+ // eslint-disable-next-line @typescript-eslint/naming-convention
82
+ const { jwks_uri } = await res.json() as { jwks_uri: string };
83
+ this.jwks = createRemoteJWKSet(new URL(jwks_uri));
84
+ }
85
+
86
+ const header = req.get('authorization');
87
+ const headerParts = header?.split(' ');
88
+ const jwt = headerParts?.length === 2 && headerParts[0]?.toLowerCase() === 'bearer' && headerParts[1];
89
+ if (!jwt) {
90
+ throw new Error('Unable to extract Bearer token from Authorization header');
91
+ }
92
+
93
+ const { payload } = await jwtVerify(jwt, this.jwks, {
94
+ audience: this.audience, // aud
95
+ clockTolerance: this.clockSkew, // exp, nbf
96
+ issuer: this.issuer, // iss
97
+ });
98
+ // console.log(JSON.stringify(payload, null, 2));
99
+
100
+ const scp = String(payload['scp'] ?? ''); // scp
101
+ if (this.scope.length > 0
102
+ && !this.scope.some((scope) => scp.includes(scope))) {
103
+ throw new Error('Scope does not match scp claim');
104
+ }
105
+
106
+ this.verifyFn(payload, (err: unknown, user: unknown, info?: string | object) => {
107
+ if (err) {
108
+ throw err as Error;
109
+ }
110
+ if (!user) {
111
+ throw new Error('No user found');
112
+ }
113
+ this.success(user, info as object);
114
+ });
115
+ } catch (error) {
116
+ const message = JSON.stringify(error);
117
+ this.fail(message);
118
+ }
119
+ }
120
+ }
package/tsconfig.json CHANGED
@@ -2,17 +2,14 @@
2
2
  "compilerOptions": {
3
3
  "declaration": true,
4
4
  "outDir": "dist",
5
-
6
- "target": "es2016",
7
- "module": "commonjs",
8
- "esModuleInterop": true,
9
5
  "forceConsistentCasingInFileNames": true,
10
-
11
- "strict": true,
12
- "skipLibCheck": true
13
6
  },
14
7
  "exclude": [
15
8
  "node_modules",
16
9
  "dist"
10
+ ],
11
+ "extends": [
12
+ "@tsconfig/strictest/tsconfig.json",
13
+ "@tsconfig/node20/tsconfig.json"
17
14
  ]
18
15
  }