@owlmeans/server-oidc-provider 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.
- package/LICENSE +21 -0
- package/README.md +848 -0
- package/build/.gitkeep +0 -0
- package/build/consts.d.ts +3 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +3 -0
- package/build/consts.js.map +1 -0
- package/build/index.d.ts +5 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -0
- package/build/middleware.d.ts +3 -0
- package/build/middleware.d.ts.map +1 -0
- package/build/middleware.js +24 -0
- package/build/middleware.js.map +1 -0
- package/build/service.d.ts +4 -0
- package/build/service.d.ts.map +1 -0
- package/build/service.js +78 -0
- package/build/service.js.map +1 -0
- package/build/types.d.ts +46 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/utils/client.d.ts +4 -0
- package/build/utils/client.d.ts.map +1 -0
- package/build/utils/client.js +31 -0
- package/build/utils/client.js.map +1 -0
- package/build/utils/config.d.ts +4 -0
- package/build/utils/config.d.ts.map +1 -0
- package/build/utils/config.js +39 -0
- package/build/utils/config.js.map +1 -0
- package/build/utils/index.d.ts +3 -0
- package/build/utils/index.d.ts.map +1 -0
- package/build/utils/index.js +3 -0
- package/build/utils/index.js.map +1 -0
- package/package.json +49 -0
- package/src/consts.ts +4 -0
- package/src/index.ts +5 -0
- package/src/middleware.ts +29 -0
- package/src/service.ts +103 -0
- package/src/types.ts +55 -0
- package/src/utils/client.ts +40 -0
- package/src/utils/config.ts +43 -0
- package/src/utils/index.ts +3 -0
- package/tsconfig.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,848 @@
|
|
|
1
|
+
# @owlmeans/server-oidc-provider
|
|
2
|
+
|
|
3
|
+
Server-side OpenID Connect Provider implementation for OwlMeans applications. This package provides a complete OIDC identity provider service built on the industry-standard `oidc-provider` library, with seamless integration into the OwlMeans server ecosystem.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `@owlmeans/server-oidc-provider` package delivers a full-featured OIDC identity provider with:
|
|
8
|
+
|
|
9
|
+
- **Complete OIDC Provider**: Full OpenID Connect 1.0 and OAuth 2.0 implementation
|
|
10
|
+
- **Fastify Integration**: Seamless integration with OwlMeans server API framework
|
|
11
|
+
- **Account Management**: Pluggable account service for user authentication
|
|
12
|
+
- **Adapter Support**: Configurable storage adapters for sessions and tokens
|
|
13
|
+
- **Client Management**: Dynamic client registration and configuration
|
|
14
|
+
- **Interaction Flows**: Customizable authentication and consent flows
|
|
15
|
+
- **Security Features**: JWT signing, HTTPS support, and secure cookie handling
|
|
16
|
+
- **Proxy Support**: Works behind reverse proxies and load balancers
|
|
17
|
+
|
|
18
|
+
This package is part of the OwlMeans OIDC ecosystem:
|
|
19
|
+
- **@owlmeans/oidc**: Core OIDC types and utilities
|
|
20
|
+
- **@owlmeans/server-oidc-provider**: Server-side OIDC provider *(this package)*
|
|
21
|
+
- **@owlmeans/server-oidc-rp**: Server-side relying party
|
|
22
|
+
- **@owlmeans/web-oidc-provider**: Web UI for OIDC provider
|
|
23
|
+
- **@owlmeans/web-oidc-rp**: Web-based relying party
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @owlmeans/server-oidc-provider oidc-provider fastify
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Dependencies
|
|
32
|
+
|
|
33
|
+
This package requires:
|
|
34
|
+
- `oidc-provider`: Industry-standard OIDC provider implementation
|
|
35
|
+
- `@owlmeans/server-api`: Server API framework with Fastify
|
|
36
|
+
- `@owlmeans/server-context`: Server context management
|
|
37
|
+
- `@owlmeans/oidc`: Core OIDC functionality
|
|
38
|
+
- `@owlmeans/config`: Configuration management
|
|
39
|
+
- `jose`: JWT operations
|
|
40
|
+
- `fastify`: HTTP server framework (peer dependency)
|
|
41
|
+
|
|
42
|
+
## Core Concepts
|
|
43
|
+
|
|
44
|
+
### OIDC Provider Service
|
|
45
|
+
|
|
46
|
+
The OIDC provider service manages the complete lifecycle of an OpenID Connect identity provider, including client management, user authentication, token issuance, and session management.
|
|
47
|
+
|
|
48
|
+
### Account Service
|
|
49
|
+
|
|
50
|
+
Pluggable account service interface that handles user authentication and account loading for the OIDC provider.
|
|
51
|
+
|
|
52
|
+
### Adapter Service
|
|
53
|
+
|
|
54
|
+
Configurable storage adapter service for persisting OIDC sessions, authorization codes, access tokens, and other provider data.
|
|
55
|
+
|
|
56
|
+
### Interaction Flows
|
|
57
|
+
|
|
58
|
+
Customizable authentication and consent flows that work with the OwlMeans client module system.
|
|
59
|
+
|
|
60
|
+
## API Reference
|
|
61
|
+
|
|
62
|
+
### Types
|
|
63
|
+
|
|
64
|
+
#### `OidcProviderService`
|
|
65
|
+
|
|
66
|
+
Main OIDC provider service interface.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
interface OidcProviderService extends InitializedService {
|
|
70
|
+
oidc: Provider // oidc-provider instance
|
|
71
|
+
update(api: ApiServer): Promise<void> // Update provider with API server
|
|
72
|
+
instance(): Provider // Get provider instance
|
|
73
|
+
getInteraction(id: string): Promise<Interaction | null> // Get interaction by ID
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### `OidcConfig`
|
|
78
|
+
|
|
79
|
+
Configuration interface for OIDC provider.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
interface OidcConfig extends OidcSharedConfig {
|
|
83
|
+
authService?: string // Authentication service name
|
|
84
|
+
basePath?: string // Base path for OIDC endpoints
|
|
85
|
+
frontBase?: string // Frontend base URL
|
|
86
|
+
clients: ClientMetadata[] // OIDC client configurations
|
|
87
|
+
customConfiguration?: Configuration // Custom oidc-provider config
|
|
88
|
+
behindProxy?: boolean // Behind reverse proxy flag
|
|
89
|
+
defaultKeys: { // Default signing keys
|
|
90
|
+
RS256: {
|
|
91
|
+
pk: string // Private key (PEM format)
|
|
92
|
+
pub?: string // Public key (PEM format)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
accountService?: string // Account service name
|
|
96
|
+
adapterService?: string // Adapter service name
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### `OidcAccountService`
|
|
101
|
+
|
|
102
|
+
Interface for user account management.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
interface OidcAccountService extends InitializedService {
|
|
106
|
+
loadById<C extends Config, T extends Context<C>>(
|
|
107
|
+
ctx: T,
|
|
108
|
+
id: string
|
|
109
|
+
): Promise<Account | undefined>
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### `OidcAdapterService`
|
|
114
|
+
|
|
115
|
+
Interface for storage adapter management.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
interface OidcAdapterService extends InitializedService {
|
|
119
|
+
instance(name: string): Adapter
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### `Config`
|
|
124
|
+
|
|
125
|
+
Extended server configuration with OIDC settings.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
interface Config extends ServerConfig, OidcConfigAppend {
|
|
129
|
+
debug: ServerConfig["debug"] & {
|
|
130
|
+
oidc?: boolean // General OIDC debugging
|
|
131
|
+
oidcServer?: boolean // Server-specific debugging
|
|
132
|
+
oidcData?: boolean // Data operation debugging
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Factory Functions
|
|
138
|
+
|
|
139
|
+
#### `createOidcProviderService(alias?: string): OidcProviderService`
|
|
140
|
+
|
|
141
|
+
Creates an OIDC provider service instance.
|
|
142
|
+
|
|
143
|
+
**Parameters:**
|
|
144
|
+
- `alias`: Service alias (default: 'oidc-provider')
|
|
145
|
+
|
|
146
|
+
**Returns:** OidcProviderService instance
|
|
147
|
+
|
|
148
|
+
**Features:**
|
|
149
|
+
- Automatic provider configuration based on context settings
|
|
150
|
+
- Integration with account and adapter services
|
|
151
|
+
- Fastify middleware integration
|
|
152
|
+
- JWT key management
|
|
153
|
+
- Client configuration management
|
|
154
|
+
|
|
155
|
+
**Example:**
|
|
156
|
+
```typescript
|
|
157
|
+
import { createOidcProviderService } from '@owlmeans/server-oidc-provider'
|
|
158
|
+
|
|
159
|
+
const oidcProvider = createOidcProviderService('main-oidc')
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### `createOidcProviderMiddleware(web?: string, oidc?: string): Middleware`
|
|
163
|
+
|
|
164
|
+
Creates middleware to integrate OIDC provider with API server.
|
|
165
|
+
|
|
166
|
+
**Parameters:**
|
|
167
|
+
- `web`: API server service alias (default: 'api-server')
|
|
168
|
+
- `oidc`: OIDC provider service alias (default: 'oidc-provider')
|
|
169
|
+
|
|
170
|
+
**Returns:** Context middleware
|
|
171
|
+
|
|
172
|
+
**Example:**
|
|
173
|
+
```typescript
|
|
174
|
+
import { createOidcProviderMiddleware } from '@owlmeans/server-oidc-provider'
|
|
175
|
+
|
|
176
|
+
const oidcMiddleware = createOidcProviderMiddleware('api', 'oidc')
|
|
177
|
+
context.registerMiddleware(oidcMiddleware)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Service Methods
|
|
181
|
+
|
|
182
|
+
#### `update(api: ApiServer): Promise<void>`
|
|
183
|
+
|
|
184
|
+
Updates the OIDC provider with the API server, registering all OIDC endpoints.
|
|
185
|
+
|
|
186
|
+
**Parameters:**
|
|
187
|
+
- `api`: API server instance to integrate with
|
|
188
|
+
|
|
189
|
+
**Process:**
|
|
190
|
+
1. Configures oidc-provider instance with context settings
|
|
191
|
+
2. Sets up account and adapter services
|
|
192
|
+
3. Configures interaction flows
|
|
193
|
+
4. Registers OIDC endpoints with Fastify server
|
|
194
|
+
|
|
195
|
+
**Example:**
|
|
196
|
+
```typescript
|
|
197
|
+
const oidcService = context.service<OidcProviderService>('oidc-provider')
|
|
198
|
+
const apiServer = context.service<ApiServer>('api')
|
|
199
|
+
|
|
200
|
+
await oidcService.update(apiServer)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### `instance(): Provider`
|
|
204
|
+
|
|
205
|
+
Gets the underlying oidc-provider instance for direct access.
|
|
206
|
+
|
|
207
|
+
**Returns:** oidc-provider Provider instance
|
|
208
|
+
|
|
209
|
+
**Example:**
|
|
210
|
+
```typescript
|
|
211
|
+
const provider = oidcService.instance()
|
|
212
|
+
|
|
213
|
+
// Access oidc-provider methods directly
|
|
214
|
+
const client = await provider.Client.find('client-id')
|
|
215
|
+
const interaction = await provider.interactionDetails(req, res)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### `getInteraction(id: string): Promise<Interaction | null>`
|
|
219
|
+
|
|
220
|
+
Retrieves an interaction by its unique identifier.
|
|
221
|
+
|
|
222
|
+
**Parameters:**
|
|
223
|
+
- `id`: Interaction identifier
|
|
224
|
+
|
|
225
|
+
**Returns:** Promise resolving to Interaction object or null
|
|
226
|
+
|
|
227
|
+
**Example:**
|
|
228
|
+
```typescript
|
|
229
|
+
const interaction = await oidcService.getInteraction('interaction-uuid')
|
|
230
|
+
if (interaction) {
|
|
231
|
+
console.log('Interaction details:', interaction.params)
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Constants
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const DEFAULT_ALIAS = 'oidc-provider' // Default service alias
|
|
239
|
+
const OIDC_ACCOUNT_SERVICE = 'oidc-account-service' // Default account service name
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Usage Examples
|
|
243
|
+
|
|
244
|
+
### Basic OIDC Provider Setup
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import { createOidcProviderService, createOidcProviderMiddleware } from '@owlmeans/server-oidc-provider'
|
|
248
|
+
import { makeServerContext } from '@owlmeans/server-context'
|
|
249
|
+
import { createApiServer } from '@owlmeans/server-api'
|
|
250
|
+
|
|
251
|
+
// Create server configuration with OIDC settings
|
|
252
|
+
const config = {
|
|
253
|
+
service: 'auth-server',
|
|
254
|
+
oidc: {
|
|
255
|
+
basePath: 'oidc',
|
|
256
|
+
clients: [
|
|
257
|
+
{
|
|
258
|
+
client_id: 'my-web-app',
|
|
259
|
+
client_secret: 'secure-client-secret',
|
|
260
|
+
redirect_uris: ['https://myapp.com/callback'],
|
|
261
|
+
response_types: ['code'],
|
|
262
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
263
|
+
scope: 'openid profile email'
|
|
264
|
+
}
|
|
265
|
+
],
|
|
266
|
+
defaultKeys: {
|
|
267
|
+
RS256: {
|
|
268
|
+
pk: `-----BEGIN PRIVATE KEY-----
|
|
269
|
+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...
|
|
270
|
+
-----END PRIVATE KEY-----`,
|
|
271
|
+
pub: `-----BEGIN PUBLIC KEY-----
|
|
272
|
+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtfGqPGwp...
|
|
273
|
+
-----END PUBLIC KEY-----`
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Create context and services
|
|
280
|
+
const context = makeServerContext(config)
|
|
281
|
+
const apiServer = createApiServer('api')
|
|
282
|
+
const oidcProvider = createOidcProviderService('oidc')
|
|
283
|
+
|
|
284
|
+
// Register services and middleware
|
|
285
|
+
context.registerService(apiServer)
|
|
286
|
+
context.registerService(oidcProvider)
|
|
287
|
+
context.registerMiddleware(createOidcProviderMiddleware('api', 'oidc'))
|
|
288
|
+
|
|
289
|
+
// Initialize and start
|
|
290
|
+
await context.configure().init()
|
|
291
|
+
await apiServer.listen()
|
|
292
|
+
|
|
293
|
+
// OIDC endpoints now available at /oidc/*
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Custom Account Service Implementation
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
import { createService } from '@owlmeans/context'
|
|
300
|
+
import type { OidcAccountService } from '@owlmeans/server-oidc-provider'
|
|
301
|
+
|
|
302
|
+
// Implement account service
|
|
303
|
+
const accountService: OidcAccountService = createService('oidc-account-service', {
|
|
304
|
+
loadById: async (ctx, id) => {
|
|
305
|
+
// Load user account from your database
|
|
306
|
+
const user = await getUserById(id)
|
|
307
|
+
|
|
308
|
+
if (!user) {
|
|
309
|
+
return undefined
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Return oidc-provider Account object
|
|
313
|
+
return {
|
|
314
|
+
accountId: user.id,
|
|
315
|
+
|
|
316
|
+
// Standard claims
|
|
317
|
+
claims: async (use, scope, claims, rejected) => {
|
|
318
|
+
const standardClaims = {
|
|
319
|
+
sub: user.id,
|
|
320
|
+
email: user.email,
|
|
321
|
+
email_verified: user.emailVerified,
|
|
322
|
+
name: user.fullName,
|
|
323
|
+
given_name: user.firstName,
|
|
324
|
+
family_name: user.lastName,
|
|
325
|
+
picture: user.avatar
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Filter claims based on scope and requested claims
|
|
329
|
+
const result = {}
|
|
330
|
+
if (scope.includes('email')) {
|
|
331
|
+
result.email = standardClaims.email
|
|
332
|
+
result.email_verified = standardClaims.email_verified
|
|
333
|
+
}
|
|
334
|
+
if (scope.includes('profile')) {
|
|
335
|
+
result.name = standardClaims.name
|
|
336
|
+
result.given_name = standardClaims.given_name
|
|
337
|
+
result.family_name = standardClaims.family_name
|
|
338
|
+
result.picture = standardClaims.picture
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return result
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
// Register account service
|
|
348
|
+
context.registerService(accountService)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Custom Storage Adapter
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
import { createService } from '@owlmeans/context'
|
|
355
|
+
import type { OidcAdapterService } from '@owlmeans/server-oidc-provider'
|
|
356
|
+
|
|
357
|
+
// Implement Redis-based adapter service
|
|
358
|
+
const adapterService: OidcAdapterService = createService('oidc-adapter-service', {
|
|
359
|
+
instance: (name) => {
|
|
360
|
+
// Return adapter for specific model (Session, AccessToken, etc.)
|
|
361
|
+
return {
|
|
362
|
+
async upsert(id, payload, expiresIn) {
|
|
363
|
+
const key = `oidc:${name}:${id}`
|
|
364
|
+
await redis.setex(key, expiresIn, JSON.stringify(payload))
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
async find(id) {
|
|
368
|
+
const key = `oidc:${name}:${id}`
|
|
369
|
+
const data = await redis.get(key)
|
|
370
|
+
return data ? JSON.parse(data) : undefined
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
async findByUserCode(userCode) {
|
|
374
|
+
// Implement user code lookup for device flow
|
|
375
|
+
const keys = await redis.keys(`oidc:${name}:*`)
|
|
376
|
+
for (const key of keys) {
|
|
377
|
+
const data = await redis.get(key)
|
|
378
|
+
const payload = JSON.parse(data)
|
|
379
|
+
if (payload.userCode === userCode) {
|
|
380
|
+
return payload
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return undefined
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
async findByUid(uid) {
|
|
387
|
+
// Implement UID-based lookup
|
|
388
|
+
const keys = await redis.keys(`oidc:${name}:*`)
|
|
389
|
+
for (const key of keys) {
|
|
390
|
+
const data = await redis.get(key)
|
|
391
|
+
const payload = JSON.parse(data)
|
|
392
|
+
if (payload.uid === uid) {
|
|
393
|
+
return payload
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return undefined
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
async destroy(id) {
|
|
400
|
+
const key = `oidc:${name}:${id}`
|
|
401
|
+
await redis.del(key)
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
async revokeByGrantId(grantId) {
|
|
405
|
+
const keys = await redis.keys(`oidc:${name}:*`)
|
|
406
|
+
for (const key of keys) {
|
|
407
|
+
const data = await redis.get(key)
|
|
408
|
+
const payload = JSON.parse(data)
|
|
409
|
+
if (payload.grantId === grantId) {
|
|
410
|
+
await redis.del(key)
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
async consume(id) {
|
|
416
|
+
const key = `oidc:${name}:${id}`
|
|
417
|
+
const data = await redis.get(key)
|
|
418
|
+
if (data) {
|
|
419
|
+
const payload = JSON.parse(data)
|
|
420
|
+
payload.consumed = Math.floor(Date.now() / 1000)
|
|
421
|
+
await redis.setex(key, payload.exp - Math.floor(Date.now() / 1000), JSON.stringify(payload))
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
// Register adapter service
|
|
429
|
+
context.registerService(adapterService)
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Advanced OIDC Configuration
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
const advancedConfig = {
|
|
436
|
+
oidc: {
|
|
437
|
+
basePath: 'auth',
|
|
438
|
+
behindProxy: true,
|
|
439
|
+
accountService: 'custom-account-service',
|
|
440
|
+
adapterService: 'redis-adapter-service',
|
|
441
|
+
|
|
442
|
+
clients: [
|
|
443
|
+
// Web application
|
|
444
|
+
{
|
|
445
|
+
client_id: 'web-app',
|
|
446
|
+
client_secret: 'web-app-secret',
|
|
447
|
+
redirect_uris: [
|
|
448
|
+
'https://app.example.com/callback',
|
|
449
|
+
'https://staging.app.example.com/callback'
|
|
450
|
+
],
|
|
451
|
+
post_logout_redirect_uris: [
|
|
452
|
+
'https://app.example.com/logout'
|
|
453
|
+
],
|
|
454
|
+
response_types: ['code'],
|
|
455
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
456
|
+
scope: 'openid profile email offline_access',
|
|
457
|
+
token_endpoint_auth_method: 'client_secret_basic'
|
|
458
|
+
},
|
|
459
|
+
|
|
460
|
+
// Mobile application (public client)
|
|
461
|
+
{
|
|
462
|
+
client_id: 'mobile-app',
|
|
463
|
+
redirect_uris: ['com.example.app://callback'],
|
|
464
|
+
response_types: ['code'],
|
|
465
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
466
|
+
scope: 'openid profile email offline_access',
|
|
467
|
+
token_endpoint_auth_method: 'none', // Public client
|
|
468
|
+
require_auth_time: true
|
|
469
|
+
},
|
|
470
|
+
|
|
471
|
+
// SPA (Single Page Application)
|
|
472
|
+
{
|
|
473
|
+
client_id: 'spa-app',
|
|
474
|
+
redirect_uris: ['https://spa.example.com/callback'],
|
|
475
|
+
response_types: ['code'],
|
|
476
|
+
grant_types: ['authorization_code'],
|
|
477
|
+
scope: 'openid profile email',
|
|
478
|
+
token_endpoint_auth_method: 'none',
|
|
479
|
+
require_auth_time: false
|
|
480
|
+
}
|
|
481
|
+
],
|
|
482
|
+
|
|
483
|
+
// Custom oidc-provider configuration
|
|
484
|
+
customConfiguration: {
|
|
485
|
+
features: {
|
|
486
|
+
devInteractions: { enabled: false },
|
|
487
|
+
deviceFlow: { enabled: true },
|
|
488
|
+
revocation: { enabled: true },
|
|
489
|
+
introspection: { enabled: true },
|
|
490
|
+
userinfo: { enabled: true }
|
|
491
|
+
},
|
|
492
|
+
|
|
493
|
+
ttl: {
|
|
494
|
+
AccessToken: 60 * 60, // 1 hour
|
|
495
|
+
AuthorizationCode: 10 * 60, // 10 minutes
|
|
496
|
+
IdToken: 60 * 60, // 1 hour
|
|
497
|
+
DeviceCode: 10 * 60, // 10 minutes
|
|
498
|
+
RefreshToken: 14 * 24 * 60 * 60 // 14 days
|
|
499
|
+
},
|
|
500
|
+
|
|
501
|
+
claims: {
|
|
502
|
+
openid: ['sub'],
|
|
503
|
+
email: ['email', 'email_verified'],
|
|
504
|
+
profile: ['name', 'family_name', 'given_name', 'picture']
|
|
505
|
+
},
|
|
506
|
+
|
|
507
|
+
scopes: ['openid', 'email', 'profile', 'offline_access']
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Interaction Flow Integration
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
import { modules } from '@owlmeans/oidc'
|
|
517
|
+
import { module, handleRequest } from '@owlmeans/module'
|
|
518
|
+
import { route, frontend } from '@owlmeans/route'
|
|
519
|
+
|
|
520
|
+
// Register OIDC interaction modules
|
|
521
|
+
context.registerModules(modules)
|
|
522
|
+
|
|
523
|
+
// Custom login interaction handler
|
|
524
|
+
const loginModule = module(
|
|
525
|
+
route('oidc-login', '/oidc/interaction/:uid/login', frontend()),
|
|
526
|
+
{
|
|
527
|
+
handle: handleRequest(async (req, ctx) => {
|
|
528
|
+
const { uid } = req.params
|
|
529
|
+
const oidcService = ctx.service<OidcProviderService>('oidc-provider')
|
|
530
|
+
|
|
531
|
+
// Get interaction details
|
|
532
|
+
const interaction = await oidcService.getInteraction(uid)
|
|
533
|
+
|
|
534
|
+
if (!interaction) {
|
|
535
|
+
throw new Error('Interaction not found')
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Return login form data
|
|
539
|
+
return {
|
|
540
|
+
interaction: {
|
|
541
|
+
uid: interaction.uid,
|
|
542
|
+
params: interaction.params,
|
|
543
|
+
prompt: interaction.prompt
|
|
544
|
+
},
|
|
545
|
+
client: interaction.params.client_id
|
|
546
|
+
}
|
|
547
|
+
})
|
|
548
|
+
}
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
context.registerModule(loginModule)
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Multi-Tenant OIDC Setup
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
// Multi-tenant configuration with entity-specific clients
|
|
558
|
+
const multiTenantConfig = {
|
|
559
|
+
oidc: {
|
|
560
|
+
clients: [
|
|
561
|
+
// Tenant A
|
|
562
|
+
{
|
|
563
|
+
client_id: 'tenant-a-web',
|
|
564
|
+
client_secret: 'tenant-a-secret',
|
|
565
|
+
redirect_uris: ['https://tenant-a.example.com/callback'],
|
|
566
|
+
response_types: ['code'],
|
|
567
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
568
|
+
scope: 'openid profile email'
|
|
569
|
+
},
|
|
570
|
+
|
|
571
|
+
// Tenant B
|
|
572
|
+
{
|
|
573
|
+
client_id: 'tenant-b-web',
|
|
574
|
+
client_secret: 'tenant-b-secret',
|
|
575
|
+
redirect_uris: ['https://tenant-b.example.com/callback'],
|
|
576
|
+
response_types: ['code'],
|
|
577
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
578
|
+
scope: 'openid profile email'
|
|
579
|
+
}
|
|
580
|
+
],
|
|
581
|
+
|
|
582
|
+
// Tenant-aware account service
|
|
583
|
+
accountService: 'multi-tenant-account-service'
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Multi-tenant account service
|
|
588
|
+
const multiTenantAccountService = createService('multi-tenant-account-service', {
|
|
589
|
+
loadById: async (ctx, id) => {
|
|
590
|
+
// Extract tenant from ID or use other tenant identification
|
|
591
|
+
const [tenantId, userId] = id.split(':')
|
|
592
|
+
|
|
593
|
+
const user = await getUserByIdAndTenant(userId, tenantId)
|
|
594
|
+
|
|
595
|
+
if (!user) {
|
|
596
|
+
return undefined
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return {
|
|
600
|
+
accountId: id,
|
|
601
|
+
claims: async (use, scope) => {
|
|
602
|
+
return {
|
|
603
|
+
sub: id,
|
|
604
|
+
email: user.email,
|
|
605
|
+
name: user.name,
|
|
606
|
+
tenant: tenantId // Custom claim
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
})
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Development and Testing Configuration
|
|
615
|
+
|
|
616
|
+
```typescript
|
|
617
|
+
// Development configuration with debugging
|
|
618
|
+
const devConfig = {
|
|
619
|
+
debug: {
|
|
620
|
+
oidc: true,
|
|
621
|
+
oidcServer: true,
|
|
622
|
+
oidcData: true
|
|
623
|
+
},
|
|
624
|
+
|
|
625
|
+
oidc: {
|
|
626
|
+
customConfiguration: {
|
|
627
|
+
features: {
|
|
628
|
+
devInteractions: { enabled: true }, // Enable dev interactions
|
|
629
|
+
},
|
|
630
|
+
|
|
631
|
+
// More permissive settings for development
|
|
632
|
+
conformIdTokenClaims: false,
|
|
633
|
+
|
|
634
|
+
// Custom cookies for development
|
|
635
|
+
cookies: {
|
|
636
|
+
keys: ['dev-cookie-secret'],
|
|
637
|
+
secure: false, // Allow non-HTTPS in development
|
|
638
|
+
sameSite: 'lax'
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
## Advanced Features
|
|
646
|
+
|
|
647
|
+
### Custom Claims and Scopes
|
|
648
|
+
|
|
649
|
+
```typescript
|
|
650
|
+
const customConfig = {
|
|
651
|
+
oidc: {
|
|
652
|
+
customConfiguration: {
|
|
653
|
+
// Define custom scopes
|
|
654
|
+
scopes: ['openid', 'email', 'profile', 'admin', 'api:read', 'api:write'],
|
|
655
|
+
|
|
656
|
+
// Map scopes to claims
|
|
657
|
+
claims: {
|
|
658
|
+
openid: ['sub'],
|
|
659
|
+
email: ['email', 'email_verified'],
|
|
660
|
+
profile: ['name', 'family_name', 'given_name', 'picture', 'locale'],
|
|
661
|
+
admin: ['role', 'permissions'],
|
|
662
|
+
'api:read': ['api_access'],
|
|
663
|
+
'api:write': ['api_access', 'write_permissions']
|
|
664
|
+
},
|
|
665
|
+
|
|
666
|
+
// Custom claim mapping
|
|
667
|
+
extraParams: ['tenant_id', 'organization'],
|
|
668
|
+
|
|
669
|
+
// Custom token format
|
|
670
|
+
formats: {
|
|
671
|
+
AccessToken: 'jwt',
|
|
672
|
+
ClientCredentials: 'jwt'
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
### Dynamic Client Registration
|
|
680
|
+
|
|
681
|
+
```typescript
|
|
682
|
+
const dynamicClientConfig = {
|
|
683
|
+
oidc: {
|
|
684
|
+
customConfiguration: {
|
|
685
|
+
features: {
|
|
686
|
+
registration: { enabled: true },
|
|
687
|
+
registrationManagement: { enabled: true }
|
|
688
|
+
},
|
|
689
|
+
|
|
690
|
+
// Client validation
|
|
691
|
+
clientDefaults: {
|
|
692
|
+
grant_types: ['authorization_code'],
|
|
693
|
+
response_types: ['code'],
|
|
694
|
+
token_endpoint_auth_method: 'client_secret_basic'
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Handle dynamic client registration
|
|
701
|
+
const clientRegistrationModule = module(
|
|
702
|
+
route('client-registration', '/oidc/reg', backend('POST')),
|
|
703
|
+
{
|
|
704
|
+
handle: handleRequest(async (req, ctx) => {
|
|
705
|
+
const oidcService = ctx.service<OidcProviderService>('oidc-provider')
|
|
706
|
+
const provider = oidcService.instance()
|
|
707
|
+
|
|
708
|
+
// Custom client validation logic
|
|
709
|
+
const clientMetadata = req.body
|
|
710
|
+
|
|
711
|
+
// Register client with oidc-provider
|
|
712
|
+
const client = await provider.Client.create(clientMetadata)
|
|
713
|
+
|
|
714
|
+
return {
|
|
715
|
+
client_id: client.clientId,
|
|
716
|
+
client_secret: client.clientSecret,
|
|
717
|
+
registration_access_token: client.registrationAccessToken
|
|
718
|
+
}
|
|
719
|
+
})
|
|
720
|
+
}
|
|
721
|
+
)
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
### Integration with External Identity Providers
|
|
725
|
+
|
|
726
|
+
```typescript
|
|
727
|
+
// Federation with external IdPs
|
|
728
|
+
const federatedConfig = {
|
|
729
|
+
oidc: {
|
|
730
|
+
customConfiguration: {
|
|
731
|
+
// Custom interaction handling for federation
|
|
732
|
+
interactions: {
|
|
733
|
+
url: async (ctx, interaction) => {
|
|
734
|
+
// Custom logic to determine if user should be redirected to external IdP
|
|
735
|
+
if (interaction.params.federated_idp) {
|
|
736
|
+
return `/oidc/federate/${interaction.params.federated_idp}/${interaction.uid}`
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return `/oidc/interaction/${interaction.uid}`
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
## Security Considerations
|
|
748
|
+
|
|
749
|
+
### Key Management
|
|
750
|
+
- Use strong RSA keys (2048-bit minimum) for JWT signing
|
|
751
|
+
- Rotate signing keys periodically
|
|
752
|
+
- Store private keys securely using environment variables or secret management
|
|
753
|
+
- Use different keys for different environments
|
|
754
|
+
|
|
755
|
+
### HTTPS and Proxy Configuration
|
|
756
|
+
- Always use HTTPS in production
|
|
757
|
+
- Configure `behindProxy` correctly when behind reverse proxies
|
|
758
|
+
- Use secure cookie settings in production
|
|
759
|
+
- Implement proper CORS policies
|
|
760
|
+
|
|
761
|
+
### Client Security
|
|
762
|
+
- Use strong client secrets for confidential clients
|
|
763
|
+
- Validate redirect URIs strictly
|
|
764
|
+
- Implement proper logout handling
|
|
765
|
+
- Use appropriate token lifetimes
|
|
766
|
+
|
|
767
|
+
### Session Management
|
|
768
|
+
- Use secure session storage
|
|
769
|
+
- Implement proper session cleanup
|
|
770
|
+
- Monitor for session hijacking attempts
|
|
771
|
+
- Use appropriate session timeouts
|
|
772
|
+
|
|
773
|
+
## Best Practices
|
|
774
|
+
|
|
775
|
+
1. **Configuration**: Use environment-specific configurations
|
|
776
|
+
2. **Account Service**: Implement robust user authentication
|
|
777
|
+
3. **Adapter Service**: Use persistent storage for production
|
|
778
|
+
4. **Client Management**: Validate client configurations thoroughly
|
|
779
|
+
5. **Security**: Follow OIDC security best practices
|
|
780
|
+
6. **Monitoring**: Implement logging and monitoring
|
|
781
|
+
7. **Testing**: Test all authentication flows thoroughly
|
|
782
|
+
|
|
783
|
+
## Error Handling
|
|
784
|
+
|
|
785
|
+
```typescript
|
|
786
|
+
try {
|
|
787
|
+
const oidcService = createOidcProviderService()
|
|
788
|
+
await oidcService.update(apiServer)
|
|
789
|
+
} catch (error) {
|
|
790
|
+
console.error('OIDC Provider setup failed:', error)
|
|
791
|
+
|
|
792
|
+
// Handle specific configuration errors
|
|
793
|
+
if (error.message.includes('missing key')) {
|
|
794
|
+
console.error('JWT signing key not configured')
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
if (error.message.includes('client')) {
|
|
798
|
+
console.error('Client configuration invalid')
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
## Integration with OwlMeans Ecosystem
|
|
804
|
+
|
|
805
|
+
### Server API Integration
|
|
806
|
+
```typescript
|
|
807
|
+
import { createApiServer } from '@owlmeans/server-api'
|
|
808
|
+
|
|
809
|
+
// OIDC provider integrates seamlessly with server API
|
|
810
|
+
const apiServer = createApiServer('api')
|
|
811
|
+
const oidcProvider = createOidcProviderService('oidc')
|
|
812
|
+
|
|
813
|
+
context.registerService(apiServer)
|
|
814
|
+
context.registerService(oidcProvider)
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
### Authentication Integration
|
|
818
|
+
```typescript
|
|
819
|
+
import { makeAuthService } from '@owlmeans/server-auth'
|
|
820
|
+
|
|
821
|
+
// Use OIDC tokens for API authentication
|
|
822
|
+
const authService = makeAuthService('auth')
|
|
823
|
+
// Configure auth service to validate OIDC tokens
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
### Context Integration
|
|
827
|
+
```typescript
|
|
828
|
+
import { makeServerContext } from '@owlmeans/server-context'
|
|
829
|
+
|
|
830
|
+
// Full integration with server context management
|
|
831
|
+
const context = makeServerContext(configWithOidc)
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
## Performance Considerations
|
|
835
|
+
|
|
836
|
+
- **Adapter Choice**: Use Redis or other fast storage for sessions
|
|
837
|
+
- **Token Formats**: Consider JWT vs opaque tokens based on use case
|
|
838
|
+
- **Caching**: Implement appropriate caching for user accounts
|
|
839
|
+
- **Database Optimization**: Optimize account and client lookups
|
|
840
|
+
- **Connection Pooling**: Use connection pooling for database operations
|
|
841
|
+
|
|
842
|
+
## Related Packages
|
|
843
|
+
|
|
844
|
+
- [`@owlmeans/oidc`](../oidc) - Core OIDC types and utilities
|
|
845
|
+
- [`@owlmeans/server-api`](../server-api) - Server API framework
|
|
846
|
+
- [`@owlmeans/server-context`](../server-context) - Server context management
|
|
847
|
+
- [`@owlmeans/server-oidc-rp`](../server-oidc-rp) - Server-side relying party
|
|
848
|
+
- [`@owlmeans/web-oidc-provider`](../web-oidc-provider) - Web UI for OIDC provider
|