@owlmeans/server-oidc-rp 0.1.0

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.
Files changed (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +520 -0
  3. package/build/.gitkeep +0 -0
  4. package/build/actions/index.d.ts +3 -0
  5. package/build/actions/index.d.ts.map +1 -0
  6. package/build/actions/index.js +3 -0
  7. package/build/actions/index.js.map +1 -0
  8. package/build/actions/init.d.ts +3 -0
  9. package/build/actions/init.d.ts.map +1 -0
  10. package/build/actions/init.js +68 -0
  11. package/build/actions/init.js.map +1 -0
  12. package/build/actions/process.d.ts +17 -0
  13. package/build/actions/process.d.ts.map +1 -0
  14. package/build/actions/process.js +80 -0
  15. package/build/actions/process.js.map +1 -0
  16. package/build/auth/consts.d.ts +2 -0
  17. package/build/auth/consts.d.ts.map +1 -0
  18. package/build/auth/consts.js +2 -0
  19. package/build/auth/consts.js.map +1 -0
  20. package/build/auth/index.d.ts +2 -0
  21. package/build/auth/index.d.ts.map +1 -0
  22. package/build/auth/index.js +2 -0
  23. package/build/auth/index.js.map +1 -0
  24. package/build/auth/plugins/index.d.ts +2 -0
  25. package/build/auth/plugins/index.d.ts.map +1 -0
  26. package/build/auth/plugins/index.js +6 -0
  27. package/build/auth/plugins/index.js.map +1 -0
  28. package/build/auth/plugins/oidc-client.d.ts +50 -0
  29. package/build/auth/plugins/oidc-client.d.ts.map +1 -0
  30. package/build/auth/plugins/oidc-client.js +211 -0
  31. package/build/auth/plugins/oidc-client.js.map +1 -0
  32. package/build/auth/plugins/types.d.ts +1 -0
  33. package/build/auth/plugins/types.d.ts.map +1 -0
  34. package/build/auth/plugins/types.js +2 -0
  35. package/build/auth/plugins/types.js.map +1 -0
  36. package/build/consts.d.ts +20 -0
  37. package/build/consts.d.ts.map +1 -0
  38. package/build/consts.js +20 -0
  39. package/build/consts.js.map +1 -0
  40. package/build/gate.d.ts +3 -0
  41. package/build/gate.d.ts.map +1 -0
  42. package/build/gate.js +22 -0
  43. package/build/gate.js.map +1 -0
  44. package/build/guard.d.ts +6 -0
  45. package/build/guard.d.ts.map +1 -0
  46. package/build/guard.js +13 -0
  47. package/build/guard.js.map +1 -0
  48. package/build/index.d.ts +8 -0
  49. package/build/index.d.ts.map +1 -0
  50. package/build/index.js +7 -0
  51. package/build/index.js.map +1 -0
  52. package/build/model/consts.d.ts +6 -0
  53. package/build/model/consts.d.ts.map +1 -0
  54. package/build/model/consts.js +7 -0
  55. package/build/model/consts.js.map +1 -0
  56. package/build/model/gate.d.ts +4 -0
  57. package/build/model/gate.d.ts.map +1 -0
  58. package/build/model/gate.js +98 -0
  59. package/build/model/gate.js.map +1 -0
  60. package/build/model/types.d.ts +25 -0
  61. package/build/model/types.d.ts.map +1 -0
  62. package/build/model/types.js +2 -0
  63. package/build/model/types.js.map +1 -0
  64. package/build/models.d.ts +1 -0
  65. package/build/models.d.ts.map +1 -0
  66. package/build/models.js +2 -0
  67. package/build/models.js.map +1 -0
  68. package/build/modules.d.ts +3 -0
  69. package/build/modules.d.ts.map +1 -0
  70. package/build/modules.js +12 -0
  71. package/build/modules.js.map +1 -0
  72. package/build/service.d.ts +3 -0
  73. package/build/service.d.ts.map +1 -0
  74. package/build/service.js +186 -0
  75. package/build/service.js.map +1 -0
  76. package/build/types.d.ts +55 -0
  77. package/build/types.d.ts.map +1 -0
  78. package/build/types.js +2 -0
  79. package/build/types.js.map +1 -0
  80. package/build/utils/auth.d.ts +5 -0
  81. package/build/utils/auth.d.ts.map +1 -0
  82. package/build/utils/auth.js +48 -0
  83. package/build/utils/auth.js.map +1 -0
  84. package/build/utils/cache.d.ts +8 -0
  85. package/build/utils/cache.d.ts.map +1 -0
  86. package/build/utils/cache.js +7 -0
  87. package/build/utils/cache.js.map +1 -0
  88. package/build/utils/index.d.ts +5 -0
  89. package/build/utils/index.d.ts.map +1 -0
  90. package/build/utils/index.js +4 -0
  91. package/build/utils/index.js.map +1 -0
  92. package/build/utils/types.d.ts +10 -0
  93. package/build/utils/types.d.ts.map +1 -0
  94. package/build/utils/types.js +2 -0
  95. package/build/utils/types.js.map +1 -0
  96. package/build/utils/wrapped.d.ts +4 -0
  97. package/build/utils/wrapped.d.ts.map +1 -0
  98. package/build/utils/wrapped.js +3 -0
  99. package/build/utils/wrapped.js.map +1 -0
  100. package/build/wrapper.d.ts +3 -0
  101. package/build/wrapper.d.ts.map +1 -0
  102. package/build/wrapper.js +75 -0
  103. package/build/wrapper.js.map +1 -0
  104. package/package.json +71 -0
  105. package/src/actions/index.ts +3 -0
  106. package/src/actions/init.ts +90 -0
  107. package/src/actions/process.ts +113 -0
  108. package/src/auth/consts.ts +2 -0
  109. package/src/auth/index.ts +2 -0
  110. package/src/auth/plugins/index.ts +8 -0
  111. package/src/auth/plugins/oidc-client.ts +246 -0
  112. package/src/auth/plugins/types.ts +0 -0
  113. package/src/consts.ts +26 -0
  114. package/src/gate.ts +29 -0
  115. package/src/guard.ts +25 -0
  116. package/src/index.ts +7 -0
  117. package/src/model/consts.ts +6 -0
  118. package/src/model/gate.ts +129 -0
  119. package/src/model/types.ts +31 -0
  120. package/src/models.ts +1 -0
  121. package/src/modules.ts +30 -0
  122. package/src/service.ts +251 -0
  123. package/src/types.ts +64 -0
  124. package/src/utils/auth.ts +67 -0
  125. package/src/utils/cache.ts +12 -0
  126. package/src/utils/index.ts +5 -0
  127. package/src/utils/types.ts +10 -0
  128. package/src/utils/wrapped.ts +6 -0
  129. package/src/wrapper.ts +92 -0
  130. package/tsconfig.json +15 -0
  131. package/tsconfig.tsbuildinfo +1 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 OwlMeans Common — Fullstack typescript framework
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,520 @@
1
+ # @owlmeans/server-oidc-rp
2
+
3
+ The **@owlmeans/server-oidc-rp** package provides server-side OpenID Connect Relying Party (RP) functionality for OwlMeans Common Libraries, enabling secure authentication and authorization through OIDC protocols in fullstack applications.
4
+
5
+ ## Purpose
6
+
7
+ This package serves as a comprehensive OIDC client implementation for server-side applications, specifically designed for:
8
+
9
+ - **OIDC Client Management**: Create and manage OpenID Connect clients with automatic discovery
10
+ - **Token Operations**: Handle authorization code grants, client credentials, token refresh, and introspection
11
+ - **Provider Integration**: Connect to multiple OIDC providers with flexible configuration
12
+ - **Account Linking**: Link external OIDC accounts with internal user profiles
13
+ - **Security Integration**: Integrate with OwlMeans authentication and authorization system
14
+ - **Module-based Authentication**: Provide OIDC guards and gates for route protection
15
+
16
+ ## Key Concepts
17
+
18
+ ### OpenID Connect Relying Party
19
+ A Relying Party (RP) is an OAuth 2.0 client application that uses OpenID Connect to verify the identity of end-users. This package implements server-side RP functionality.
20
+
21
+ ### Provider Configuration
22
+ The package supports multiple OIDC providers with configuration including discovery URLs, client credentials, and service mappings.
23
+
24
+ ### Account Linking
25
+ Enables linking external OIDC provider accounts with internal user accounts, supporting both automatic linking and manual credential mapping.
26
+
27
+ ### Token Management
28
+ Comprehensive token lifecycle management including acquisition, refresh, introspection, and validation.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ npm install @owlmeans/server-oidc-rp
34
+ ```
35
+
36
+ ## API Reference
37
+
38
+ ### Types
39
+
40
+ #### `OidcClientService`
41
+ Main service interface for OIDC client operations.
42
+
43
+ ```typescript
44
+ interface OidcClientService extends InitializedService {
45
+ getConfiguration(clientId: string | Partial<OidcProviderConfig>): Promise<OidcClientDescriptor>
46
+ getClient(clientId: string | OidcClientDescriptor | Partial<OidcProviderConfig>): Promise<OidcClientAdapter>
47
+ getConfig(clientId: string | Partial<OidcProviderConfig>): Promise<OidcProviderConfig | undefined>
48
+ getDefault(): string | undefined
49
+
50
+ registerTemporaryProvider(config: OidcProviderConfig): OidcProviderConfig
51
+ unregisterTemporaryProvider(params: Partial<OidcProviderConfig>): void
52
+ hasProvider(params: Partial<OidcProviderConfig>): boolean
53
+ entityToClientId(params: Partial<OidcProviderConfig>): string
54
+
55
+ providerApi(): ProviderApiService | null
56
+ accountLinking(): AccountLinkingService | null
57
+ }
58
+ ```
59
+
60
+ #### `OidcClientAdapter`
61
+ Interface for individual OIDC client instances.
62
+
63
+ ```typescript
64
+ interface OidcClientAdapter {
65
+ getMetadata(): ServerMetadata
66
+ getClientId(): string
67
+ getConfig(): OidcProviderConfig
68
+ makeAuthUrl(params: Record<string, string>): string
69
+ grantWithCredentials(): Promise<TokenSet>
70
+ grantWithCode(currentUrl: string, checks: AuthorizationCodeGrantChecks, params: Record<string, string>): Promise<TokenSet>
71
+ refresh(tokenSet: TokenSetParameters | string): Promise<TokenSetParameters>
72
+ introspect(tokenSet: TokenSetParameters, type?: string): Promise<IntrospectionResponse>
73
+ }
74
+ ```
75
+
76
+ #### `AccountLinkingService`
77
+ Service interface for linking OIDC accounts with internal profiles.
78
+
79
+ ```typescript
80
+ interface AccountLinkingService extends InitializedService {
81
+ getLinkedProfile(details: ProviderProfileDetails): Promise<AuthPayload | null>
82
+ linkProfile(details: ProviderProfileDetails, meta: AccountMeta): Promise<AuthPayload>
83
+ linkCredentials(details: ProviderProfileDetails): Promise<AuthPayload>
84
+ getOwnerProfiles(entityId: string): Promise<Profile[]>
85
+ getOwnerCredentials(userId: string, entityId?: string, type?: string): Promise<AuthCredentials | undefined>
86
+ }
87
+ ```
88
+
89
+ #### `ProviderApiService`
90
+ Service interface for interacting with OIDC provider APIs.
91
+
92
+ ```typescript
93
+ interface ProviderApiService extends InitializedService {
94
+ getUserDetails(token: string, userId: string): Promise<OidcUserDetails>
95
+ getSettings(token: string, realm: string): Promise<OidcProviderSettings>
96
+ }
97
+ ```
98
+
99
+ #### `Config`
100
+ Configuration interface extending ServerConfig.
101
+
102
+ ```typescript
103
+ interface Config extends ServerConfig, WithSharedConfig {
104
+ oidc: OidcRpConfig
105
+ }
106
+
107
+ interface OidcRpConfig extends OidcSharedConfig {
108
+ accountLinkingService?: string
109
+ providerApiService?: string
110
+ }
111
+ ```
112
+
113
+ ### Factory Functions
114
+
115
+ #### `makeOidcClientService(alias?: string): OidcClientService`
116
+
117
+ Creates an OIDC client service instance.
118
+
119
+ **Parameters:**
120
+ - `alias`: Service alias (defaults to 'oidc-client')
121
+
122
+ **Returns:** OidcClientService instance
123
+
124
+ ```typescript
125
+ import { makeOidcClientService } from '@owlmeans/server-oidc-rp'
126
+
127
+ const oidcService = makeOidcClientService('my-oidc-client')
128
+ ```
129
+
130
+ ### Guard Functions
131
+
132
+ #### `appendOidcGuard<C extends Config, T extends Context<C>>(context: T, opts?: OidcGuardOptions): T`
133
+
134
+ Appends OIDC guard functionality to a context.
135
+
136
+ **Parameters:**
137
+ - `context`: Server context instance
138
+ - `opts`: Optional OIDC guard configuration
139
+
140
+ **Returns:** Enhanced context with OIDC guard capabilities
141
+
142
+ ```typescript
143
+ import { appendOidcGuard } from '@owlmeans/server-oidc-rp'
144
+
145
+ const enhancedContext = appendOidcGuard(context, {
146
+ defaultProvider: 'keycloak'
147
+ })
148
+ ```
149
+
150
+ #### `setupOidcGuard(modules: CommonModule[], coguards?: string | string[]): void`
151
+
152
+ Sets up OIDC guard functionality for modules.
153
+
154
+ **Parameters:**
155
+ - `modules`: Array of modules to enhance
156
+ - `coguards`: Optional co-guard configurations
157
+
158
+ ```typescript
159
+ import { setupOidcGuard } from '@owlmeans/server-oidc-rp'
160
+
161
+ setupOidcGuard(modules, ['authenticated', 'oidc-verified'])
162
+ ```
163
+
164
+ ### Module Setup
165
+
166
+ #### `setupAuthServiceModules(modules: CommonModule[], serviceAlias: string, prefix?: string): void`
167
+
168
+ Sets up authentication service modules with OIDC routes.
169
+
170
+ **Parameters:**
171
+ - `modules`: Array of modules to add OIDC routes to
172
+ - `serviceAlias`: Service alias for the routes
173
+ - `prefix`: URL prefix for OIDC routes (defaults to 'oidc-api')
174
+
175
+ ```typescript
176
+ import { setupAuthServiceModules } from '@owlmeans/server-oidc-rp'
177
+
178
+ setupAuthServiceModules(modules, 'auth-service', 'oidc-api')
179
+ ```
180
+
181
+ ### Constants
182
+
183
+ #### Service Aliases
184
+ ```typescript
185
+ const DEFAULT_ALIAS = 'oidc-client'
186
+ const DEF_OIDC_ACCOUNT_LINKING = 'oidc-consumer-account-linking'
187
+ const DEF_OIDC_PROVIDER_API = 'oidc-consumer-provider-api'
188
+ ```
189
+
190
+ #### Route Definitions
191
+ ```typescript
192
+ const authService = {
193
+ provider: {
194
+ list: 'external-auth:provider:list'
195
+ },
196
+ auth: {
197
+ update: 'external-auth:auth:update'
198
+ }
199
+ }
200
+ ```
201
+
202
+ #### Cache and Timing Constants
203
+ ```typescript
204
+ const OIDC_TOKEN_STORE = 'oidc-token-store'
205
+ const PROVIDER_CACHE_TTL = (5 * 60 - 1) * 1000 // ~5 minutes
206
+ const OIDC_AUTH_LIFTETIME = 24 * 3600 * 1000 // 24 hours
207
+ const OIDC_WRAP_FRESHNESS = (5 * 60 - 1) * 1000 // ~5 minutes
208
+ ```
209
+
210
+ ## Usage Examples
211
+
212
+ ### Basic OIDC Client Setup
213
+
214
+ ```typescript
215
+ import { makeOidcClientService, setupOidcGuard } from '@owlmeans/server-oidc-rp'
216
+ import { makeServerContext, makeServerConfig } from '@owlmeans/server-context'
217
+
218
+ // Create server context with OIDC configuration
219
+ const config = makeServerConfig('auth-server', {
220
+ oidc: {
221
+ providers: [{
222
+ clientId: 'my-app',
223
+ secret: 'client-secret',
224
+ service: 'keycloak',
225
+ basePath: '/realms/my-realm',
226
+ def: true
227
+ }]
228
+ }
229
+ })
230
+
231
+ const context = makeServerContext(config)
232
+
233
+ // Create and register OIDC client service
234
+ const oidcService = makeOidcClientService()
235
+ context.registerService(oidcService)
236
+
237
+ // Initialize context
238
+ await context.configure().init()
239
+ ```
240
+
241
+ ### Using OIDC Client
242
+
243
+ ```typescript
244
+ // Get the OIDC service
245
+ const oidcService = context.service<OidcClientService>('oidc-client')
246
+
247
+ // Get OIDC client for default provider
248
+ const defaultClientId = oidcService.getDefault()
249
+ const client = await oidcService.getClient(defaultClientId!)
250
+
251
+ // Create authorization URL
252
+ const authUrl = client.makeAuthUrl({
253
+ redirect_uri: 'https://myapp.com/callback',
254
+ scope: 'openid profile email',
255
+ state: 'random-state'
256
+ })
257
+
258
+ console.log('Redirect user to:', authUrl)
259
+ ```
260
+
261
+ ### Authorization Code Flow
262
+
263
+ ```typescript
264
+ // Handle callback from OIDC provider
265
+ app.get('/callback', async (req, res) => {
266
+ const client = await oidcService.getClient('my-app')
267
+
268
+ try {
269
+ const tokenSet = await client.grantWithCode(
270
+ req.url,
271
+ {
272
+ state: req.query.state,
273
+ code_verifier: 'stored-code-verifier' // if using PKCE
274
+ },
275
+ { flow: 'authorization_code' }
276
+ )
277
+
278
+ console.log('Access token:', tokenSet.access_token)
279
+ console.log('ID token:', tokenSet.id_token)
280
+
281
+ // Store tokens securely
282
+ res.redirect('/dashboard')
283
+ } catch (error) {
284
+ console.error('OIDC authentication failed:', error)
285
+ res.redirect('/login?error=auth_failed')
286
+ }
287
+ })
288
+ ```
289
+
290
+ ### Client Credentials Flow
291
+
292
+ ```typescript
293
+ // Get access token using client credentials
294
+ const client = await oidcService.getClient('service-account')
295
+ const tokenSet = await client.grantWithCredentials()
296
+
297
+ console.log('Service access token:', tokenSet.access_token)
298
+
299
+ // Use token for API calls
300
+ const response = await fetch('https://api.service.com/data', {
301
+ headers: {
302
+ 'Authorization': `Bearer ${tokenSet.access_token}`
303
+ }
304
+ })
305
+ ```
306
+
307
+ ### Token Refresh
308
+
309
+ ```typescript
310
+ // Refresh an expired token
311
+ try {
312
+ const newTokenSet = await client.refresh(oldTokenSet)
313
+ console.log('New access token:', newTokenSet.access_token)
314
+ } catch (error) {
315
+ console.error('Token refresh failed:', error)
316
+ // Redirect to login
317
+ }
318
+ ```
319
+
320
+ ### Token Introspection
321
+
322
+ ```typescript
323
+ // Validate and introspect a token
324
+ const introspection = await client.introspect(tokenSet, 'access_token')
325
+
326
+ if (introspection.active) {
327
+ console.log('Token is valid')
328
+ console.log('Token expires at:', new Date(introspection.exp! * 1000))
329
+ console.log('Token subject:', introspection.sub)
330
+ } else {
331
+ console.log('Token is invalid or expired')
332
+ }
333
+ ```
334
+
335
+ ### Multiple Provider Configuration
336
+
337
+ ```typescript
338
+ const config = makeServerConfig('multi-provider-app', {
339
+ oidc: {
340
+ providers: [
341
+ {
342
+ clientId: 'keycloak-client',
343
+ secret: 'secret1',
344
+ service: 'keycloak',
345
+ basePath: '/realms/main',
346
+ def: true
347
+ },
348
+ {
349
+ clientId: 'google-client',
350
+ secret: 'secret2',
351
+ discoveryUrl: 'https://accounts.google.com/.well-known/openid_configuration'
352
+ }
353
+ ]
354
+ }
355
+ })
356
+
357
+ // Get specific provider clients
358
+ const keycloakClient = await oidcService.getClient('keycloak-client')
359
+ const googleClient = await oidcService.getClient('google-client')
360
+ ```
361
+
362
+ ### Account Linking
363
+
364
+ ```typescript
365
+ // Setup account linking service
366
+ const accountLinkingService: AccountLinkingService = createService('account-linking', {
367
+ getLinkedProfile: async (details) => {
368
+ // Check if external profile is already linked
369
+ const existing = await database.findLinkedAccount(details.sub, details.iss)
370
+ return existing ? existing.authPayload : null
371
+ },
372
+
373
+ linkProfile: async (details, meta) => {
374
+ // Link external account to existing user
375
+ const user = await database.findUserByUsername(meta.username)
376
+ await database.linkExternalAccount(user.id, details)
377
+ return createAuthPayload(user)
378
+ },
379
+
380
+ linkCredentials: async (details) => {
381
+ // Auto-link based on email or other criteria
382
+ const user = await database.findUserByEmail(details.email)
383
+ if (user) {
384
+ await database.linkExternalAccount(user.id, details)
385
+ return createAuthPayload(user)
386
+ }
387
+ throw new Error('No matching user found')
388
+ }
389
+ })
390
+
391
+ context.registerService(accountLinkingService)
392
+ ```
393
+
394
+ ### Module Protection with OIDC Guards
395
+
396
+ ```typescript
397
+ import { module, guard } from '@owlmeans/module'
398
+ import { route } from '@owlmeans/route'
399
+
400
+ // Create protected modules
401
+ const protectedModules = [
402
+ module(route('profile', '/profile'), guard('oidc-authenticated')),
403
+ module(route('admin', '/admin'), guard(['oidc-authenticated', 'admin-role']))
404
+ ]
405
+
406
+ // Setup OIDC guards
407
+ setupOidcGuard(protectedModules, 'oidc-authenticated')
408
+ ```
409
+
410
+ ## Error Handling
411
+
412
+ The package throws `AuthManagerError` for various OIDC-related issues:
413
+
414
+ ```typescript
415
+ import { AuthManagerError } from '@owlmeans/auth'
416
+
417
+ try {
418
+ const client = await oidcService.getClient('invalid-client')
419
+ } catch (error) {
420
+ if (error instanceof AuthManagerError) {
421
+ switch (error.message) {
422
+ case 'oidc.client.basepath':
423
+ console.error('OIDC provider base path not configured')
424
+ break
425
+ case 'oidc.client.service':
426
+ console.error('OIDC provider service not specified')
427
+ break
428
+ case 'oidc.client.client-id':
429
+ console.error('OIDC client ID missing')
430
+ break
431
+ case 'oidc.client.secert':
432
+ console.error('OIDC client secret missing')
433
+ break
434
+ default:
435
+ console.error('OIDC error:', error.message)
436
+ }
437
+ }
438
+ }
439
+ ```
440
+
441
+ ## Integration Patterns
442
+
443
+ ### Express.js Integration
444
+
445
+ ```typescript
446
+ import express from 'express'
447
+ import { makeOidcClientService } from '@owlmeans/server-oidc-rp'
448
+
449
+ const app = express()
450
+
451
+ // OIDC login route
452
+ app.get('/login/:provider?', async (req, res) => {
453
+ const providerId = req.params.provider || oidcService.getDefault()
454
+ const client = await oidcService.getClient(providerId!)
455
+
456
+ const authUrl = client.makeAuthUrl({
457
+ redirect_uri: `${req.protocol}://${req.get('host')}/callback`,
458
+ scope: 'openid profile email',
459
+ state: generateState()
460
+ })
461
+
462
+ res.redirect(authUrl)
463
+ })
464
+
465
+ // OIDC callback route
466
+ app.get('/callback', async (req, res) => {
467
+ // Handle authorization code flow as shown above
468
+ })
469
+ ```
470
+
471
+ ### API Gateway Integration
472
+
473
+ ```typescript
474
+ // Create API gateway with OIDC token validation
475
+ const validateToken = async (req: Request) => {
476
+ const authHeader = req.headers.authorization
477
+ if (!authHeader?.startsWith('Bearer ')) {
478
+ throw new Error('Missing or invalid authorization header')
479
+ }
480
+
481
+ const token = authHeader.substring(7)
482
+ const client = await oidcService.getClient(oidcService.getDefault()!)
483
+
484
+ const introspection = await client.introspect({ access_token: token })
485
+ if (!introspection.active) {
486
+ throw new Error('Invalid or expired token')
487
+ }
488
+
489
+ return introspection
490
+ }
491
+ ```
492
+
493
+ ## Best Practices
494
+
495
+ 1. **Secure Configuration**: Store client secrets securely and never expose them in client-side code
496
+ 2. **Token Storage**: Store tokens securely with appropriate encryption and expiration handling
497
+ 3. **Error Handling**: Implement comprehensive error handling for all OIDC operations
498
+ 4. **Provider Discovery**: Use OIDC discovery when possible for automatic configuration
499
+ 5. **Token Validation**: Always validate tokens before using them for authorization decisions
500
+ 6. **Account Linking**: Implement secure account linking with proper user consent flows
501
+ 7. **Monitoring**: Monitor OIDC operations for security and performance issues
502
+
503
+ ## Dependencies
504
+
505
+ This package depends on:
506
+ - `@owlmeans/auth` - Authentication framework
507
+ - `@owlmeans/auth-common` - Common authentication utilities
508
+ - `@owlmeans/context` - Context management
509
+ - `@owlmeans/oidc` - Core OIDC functionality
510
+ - `@owlmeans/server-context` - Server context management
511
+ - `@owlmeans/server-module` - Server module system
512
+ - `openid-client` - OpenID Connect client library
513
+ - `jose` - JSON Web Token utilities
514
+
515
+ ## Related Packages
516
+
517
+ - [`@owlmeans/web-oidc-rp`](../web-oidc-rp) - Web client OIDC Relying Party
518
+ - [`@owlmeans/oidc`](../oidc) - Core OIDC functionality
519
+ - [`@owlmeans/server-auth`](../server-auth) - Server authentication framework
520
+ - [`@owlmeans/auth`](../auth) - Authentication foundation
package/build/.gitkeep ADDED
File without changes
@@ -0,0 +1,3 @@
1
+ export * from './process.js';
2
+ export * from './init.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/actions/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA"}
@@ -0,0 +1,3 @@
1
+ export * from './process.js';
2
+ export * from './init.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/actions/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA"}
@@ -0,0 +1,3 @@
1
+ import type { RefedModuleHandler } from '@owlmeans/server-module';
2
+ export declare const init: RefedModuleHandler;
3
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/actions/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAiBjE,eAAO,MAAM,IAAI,EAAE,kBAwEjB,CAAA"}
@@ -0,0 +1,68 @@
1
+ import { handleBody } from '@owlmeans/server-api';
2
+ import { AuthenPayloadError, AuthUnknown, DISPATCHER } from '@owlmeans/auth';
3
+ import { assertContext } from '@owlmeans/context';
4
+ import { authService, DEFAULT_ALIAS } from '../consts.js';
5
+ // import { PROVIDER_CACHE_TTL } from '../consts.js'
6
+ // import type { Client } from 'openid-client'
7
+ import { base64urlnopad as base64 } from '@scure/base';
8
+ import { randomBytes } from '@noble/hashes/utils';
9
+ import { sha256 } from '@noble/hashes/sha256';
10
+ import { cache, verifierId } from '../utils/cache.js';
11
+ import { AUTHEN_TIMEFRAME } from '@owlmeans/server-auth';
12
+ export const init = handleBody(async (body, ctx) => {
13
+ if (body.entity == null) {
14
+ throw new AuthenPayloadError('entity');
15
+ }
16
+ const entityId = body.entity;
17
+ const context = assertContext(ctx);
18
+ const oidc = context.service(DEFAULT_ALIAS);
19
+ let client;
20
+ // if (!oidc.hasProvider({ entityId })) {
21
+ /**
22
+ * @TODO We need to move it to some remote resource.
23
+ * And make oidc service itself use such a resource to get required client.
24
+ */
25
+ const [providers] = await context.module(authService.provider.list).call({
26
+ params: { service: context.cfg.alias ?? context.cfg.service },
27
+ query: { entityId }
28
+ });
29
+ if (providers.length < 1) {
30
+ throw new AuthUnknown();
31
+ }
32
+ console.log("\n\n ************* \n Providers we get from remote:", providers);
33
+ const provider = providers.find(p => p.def === true) ?? providers.shift();
34
+ console.log("\n\nProvider we choose:", provider);
35
+ console.log("************* \n\n");
36
+ if (provider == null) {
37
+ throw new AuthUnknown();
38
+ }
39
+ oidc.registerTemporaryProvider(provider);
40
+ client = await oidc.getClient({ clientId: provider.clientId, entityId });
41
+ // Cache provider for a while - we dont actually need to clean it up
42
+ // setTimeout(() => provider && oidc.unregisterTemporaryProvider(provider), PROVIDER_CACHE_TTL)
43
+ // } else {
44
+ // client = await oidc.getClient({
45
+ // entityId, clientId: oidc.entityToClientId({ entityId })
46
+ // })
47
+ // }
48
+ const verifier = base64.encode(randomBytes(32));
49
+ const challenge = base64.encode(sha256(verifier));
50
+ console.log("\n\n We create verifierID: ", verifierId(challenge), verifier, " with client: ", client.getClientId(), "\n\n");
51
+ await cache(context).create({
52
+ id: verifierId(challenge),
53
+ verifier,
54
+ client: client.getClientId(),
55
+ entityId,
56
+ }, { ttl: AUTHEN_TIMEFRAME / 1000 });
57
+ const [dispatcherUrl] = await context.module(DISPATCHER).call();
58
+ const cfg = client.getConfig();
59
+ const url = client.makeAuthUrl({
60
+ scope: `openid profile email ${cfg?.extraScopes ?? ''}`,
61
+ code_challenge: challenge,
62
+ code_challenge_method: 'S256',
63
+ redirect_uri: dispatcherUrl,
64
+ });
65
+ console.log('Url we got', url);
66
+ return url;
67
+ });
68
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/actions/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { RefedModuleHandler } from '@owlmeans/server-module';
2
+ /**
3
+ * Authentication method links together OIDC and OwlMeans auth.
4
+ * 1. We authenticate OIDC redirect back params with the classic OIDC client.
5
+ * 2. We temporary store on the server refresh token and OIDC token.
6
+ * 3. We supply the client with perked OwlMeans Auth token that in fact
7
+ * is the set of OIDC tokens (except refresh token - we keep it here?)
8
+ * 4. We used it in a centralized way to check permissions over OwlMeans Auth
9
+ * central service irrespectively is it OIDC or our custom permissions engine
10
+ * (this is useful to properly support paid accounts and bridge the underlying
11
+ * IAM system implemetation - abstract from it).
12
+ * 5. The method ends with production of OwlMeans authorization token.
13
+ *
14
+ * ! There is also token renewal mechanism that should be implemented nearby.
15
+ */
16
+ export declare const authenticate: RefedModuleHandler;
17
+ //# sourceMappingURL=process.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/actions/process.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAkBjE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,EAAE,kBAgFzB,CAAA"}