@sphereon/ssi-express-support 0.37.1-next.5 → 0.37.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sphereon/ssi-express-support",
3
- "version": "0.37.1-next.5+14e4bec8",
3
+ "version": "0.37.1",
4
4
  "source": "src/index.ts",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -23,7 +23,7 @@
23
23
  "start:energyshr": "cross-env ENVIRONMENT=EnergySHR NODE_ENV=energyshr node --experimental-specifier-resolution=node --loader ts-node/esm __tests__/index.ts"
24
24
  },
25
25
  "dependencies": {
26
- "@sphereon/ssi-types": "0.37.1-next.5+14e4bec8",
26
+ "@sphereon/ssi-types": "0.37.1",
27
27
  "body-parser": "^1.20.2",
28
28
  "casbin": "^5.30.0",
29
29
  "cookie-session": "^2.1.0",
@@ -51,15 +51,20 @@
51
51
  "@types/passport": "^1.0.16",
52
52
  "@types/passport-azure-ad": "^4.3.6",
53
53
  "@types/passport-http-bearer": "^1.0.41",
54
+ "@types/passport-jwt": "^4.0.1",
54
55
  "@types/qs": "^6.9.15",
55
56
  "cross-env": "^7.0.3",
56
57
  "jose": "^4.15.9",
58
+ "jwks-rsa": "^3.2.2",
59
+ "passport-jwt": "^4.0.1",
57
60
  "typescript": "5.8.3"
58
61
  },
59
62
  "peerDependencies": {
60
63
  "@noble/hashes": "1.6.1",
64
+ "jwks-rsa": "^3.2.2",
61
65
  "passport-azure-ad": "^4.3.5",
62
- "passport-http-bearer": "^1.0.1"
66
+ "passport-http-bearer": "^1.0.1",
67
+ "passport-jwt": "^4.0.1"
63
68
  },
64
69
  "peerDependenciesMeta": {
65
70
  "passport-http-bearer": {
@@ -70,6 +75,12 @@
70
75
  },
71
76
  "passport-azure-ad": {
72
77
  "optional": true
78
+ },
79
+ "jwks-rsa": {
80
+ "optional": true
81
+ },
82
+ "passport-jwt": {
83
+ "optional": true
73
84
  }
74
85
  },
75
86
  "files": [
@@ -89,5 +100,5 @@
89
100
  "SSI",
90
101
  "Agent"
91
102
  ],
92
- "gitHead": "14e4bec846cf7534ab57d0f51bf480427933c132"
103
+ "gitHead": "f77778193dc9235727d306be0449c5bf05b63cbe"
93
104
  }
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './entra-id-auth'
2
+ export * from './oidc-bearer-auth'
2
3
  export * from './static-bearer-auth'
3
4
  export * from './auth-utils'
4
5
  export * from './express-builders'
@@ -0,0 +1,148 @@
1
+ import passport from 'passport'
2
+
3
+ export type OIDCAlgorithm = 'RS256' | 'RS384' | 'RS512' | 'ES256' | 'ES384' | 'ES512' | 'PS256' | 'PS384' | 'PS512'
4
+
5
+ export interface IOIDCBearerOptions {
6
+ issuer: string
7
+ audience?: string | string[]
8
+ jwksUri?: string
9
+ algorithms?: OIDCAlgorithm[]
10
+ }
11
+
12
+ export interface IOIDCTokenPayload {
13
+ /** Issuer identifier */
14
+ iss?: string
15
+ /** Subject identifier */
16
+ sub?: string
17
+ /** Audience(s) */
18
+ aud?: string | string[]
19
+ /** Expiration time */
20
+ exp?: number
21
+ /** Not before */
22
+ nbf?: number
23
+ /** Issued at */
24
+ iat?: number
25
+ /** JWT ID */
26
+ jti?: string
27
+ /** Authorized party */
28
+ azp?: string
29
+ /** Scope */
30
+ scope?: string
31
+ /** Client ID */
32
+ client_id?: string
33
+ /** Additional claims */
34
+ [key: string]: unknown
35
+ }
36
+
37
+ export class OIDCBearerAuth {
38
+ private readonly strategy: string
39
+ private options: Partial<IOIDCBearerOptions> = {}
40
+
41
+ public static init(strategy: string) {
42
+ return new OIDCBearerAuth(strategy)
43
+ }
44
+
45
+ private constructor(strategy: string) {
46
+ this.strategy = strategy
47
+ }
48
+
49
+ public withIssuer(issuer: string): this {
50
+ this.options = { ...this.options, issuer }
51
+ return this
52
+ }
53
+
54
+ public withAudience(audience: string | string[]): this {
55
+ this.options = { ...this.options, audience }
56
+ return this
57
+ }
58
+
59
+ public withJwksUri(jwksUri: string): this {
60
+ this.options = { ...this.options, jwksUri }
61
+ return this
62
+ }
63
+
64
+ public withAlgorithms(algorithms: OIDCAlgorithm[]): this {
65
+ this.options = { ...this.options, algorithms }
66
+ return this
67
+ }
68
+
69
+ public withOptions(options: Partial<IOIDCBearerOptions>): this {
70
+ this.options = { ...this.options, ...options }
71
+ return this
72
+ }
73
+
74
+ async connectPassport(): Promise<void> {
75
+ const { issuer, audience, algorithms } = this.options
76
+
77
+ if (!issuer) {
78
+ return Promise.reject(new Error('No issuer supplied for OIDC Bearer Auth'))
79
+ }
80
+
81
+ let jwksUri = this.options.jwksUri
82
+ if (!jwksUri) {
83
+ jwksUri = await this.discoverJwksUri(issuer)
84
+ }
85
+
86
+ try {
87
+ const [passportJwt, jwksRsa] = await Promise.all([import('passport-jwt'), import('jwks-rsa')])
88
+
89
+ const { Strategy: JwtStrategy, ExtractJwt } = passportJwt
90
+ const { passportJwtSecret } = jwksRsa
91
+
92
+ const jwtOptions = {
93
+ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
94
+ secretOrKeyProvider: passportJwtSecret({
95
+ cache: true,
96
+ rateLimit: true,
97
+ jwksRequestsPerMinute: 5,
98
+ jwksUri,
99
+ }),
100
+ issuer,
101
+ audience,
102
+ algorithms: algorithms ?? (['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512'] as OIDCAlgorithm[]),
103
+ }
104
+
105
+ passport.use(
106
+ this.strategy,
107
+ new JwtStrategy(jwtOptions, (payload: IOIDCTokenPayload, done: (error: any, user?: any, info?: any) => void) => {
108
+ if (payload) {
109
+ return done(null, payload)
110
+ }
111
+ return done('Bearer token not found or incorrect', null)
112
+ })
113
+ )
114
+ } catch (error) {
115
+ console.error('Failed to initialize OIDC Bearer Auth:', error)
116
+ return Promise.reject(
117
+ new Error(
118
+ 'Could not create JWT bearer strategy. Did you include "passport-jwt" and "jwks-rsa" dependencies in package.json?',
119
+ { cause: error }
120
+ )
121
+ )
122
+ }
123
+ }
124
+
125
+ private async discoverJwksUri(issuer: string): Promise<string> {
126
+ const wellKnownUrl = `${issuer}${issuer.endsWith('/') ? '' : '/'}.well-known/openid-configuration`
127
+
128
+ try {
129
+ const response = await fetch(wellKnownUrl)
130
+ if (!response.ok) {
131
+ return Promise.reject(
132
+ new Error(`Failed to fetch OIDC configuration from ${wellKnownUrl}: ${response.status} ${response.statusText}`)
133
+ )
134
+ }
135
+
136
+ const config = (await response.json()) as { jwks_uri?: string }
137
+ if (!config.jwks_uri) {
138
+ return Promise.reject(new Error(`OIDC configuration at ${wellKnownUrl} does not contain jwks_uri`))
139
+ }
140
+
141
+ return config.jwks_uri
142
+ } catch (error) {
143
+ return Promise.reject(
144
+ new Error(`Failed to discover JWKS URI from OIDC configuration at ${wellKnownUrl}`, { cause: error })
145
+ )
146
+ }
147
+ }
148
+ }