@owlmeans/web-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.
- package/LICENSE +21 -0
- package/README.md +515 -0
- package/build/.gitkeep +0 -0
- package/build/auth/plugins/index.d.ts +4 -0
- package/build/auth/plugins/index.d.ts.map +1 -0
- package/build/auth/plugins/index.js +6 -0
- package/build/auth/plugins/index.js.map +1 -0
- package/build/auth/plugins/oidc-client.d.ts +3 -0
- package/build/auth/plugins/oidc-client.d.ts.map +1 -0
- package/build/auth/plugins/oidc-client.js +97 -0
- package/build/auth/plugins/oidc-client.js.map +1 -0
- package/build/components/dispatcher.d.ts +2 -0
- package/build/components/dispatcher.d.ts.map +1 -0
- package/build/components/dispatcher.js +58 -0
- package/build/components/dispatcher.js.map +1 -0
- package/build/components/index.d.ts +2 -0
- package/build/components/index.d.ts.map +1 -0
- package/build/components/index.js +2 -0
- package/build/components/index.js.map +1 -0
- package/build/consts.d.ts +7 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +8 -0
- package/build/consts.js.map +1 -0
- package/build/guard.d.ts +6 -0
- package/build/guard.d.ts.map +1 -0
- package/build/guard.js +18 -0
- package/build/guard.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +5 -0
- package/build/index.js.map +1 -0
- package/build/service.d.ts +3 -0
- package/build/service.d.ts.map +1 -0
- package/build/service.js +112 -0
- package/build/service.js.map +1 -0
- package/build/types.d.ts +25 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/package.json +60 -0
- package/src/auth/plugins/index.ts +8 -0
- package/src/auth/plugins/oidc-client.tsx +114 -0
- package/src/components/dispatcher.tsx +61 -0
- package/src/components/index.ts +2 -0
- package/src/consts.ts +8 -0
- package/src/guard.ts +31 -0
- package/src/index.ts +6 -0
- package/src/service.ts +147 -0
- package/src/types.ts +32 -0
- package/tsconfig.json +16 -0
- 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,515 @@
|
|
|
1
|
+
# @owlmeans/web-oidc-rp
|
|
2
|
+
|
|
3
|
+
The **@owlmeans/web-oidc-rp** package provides web client-side OpenID Connect Relying Party (RP) functionality for OwlMeans Common Libraries, enabling secure authentication and authorization through OIDC protocols in React-based frontend applications.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This package serves as a comprehensive OIDC client implementation for web applications, specifically designed for:
|
|
8
|
+
|
|
9
|
+
- **Frontend OIDC Authentication**: Handle OIDC authentication flows in React applications
|
|
10
|
+
- **Flow Integration**: Integrate with OwlMeans flow system for complex authentication workflows
|
|
11
|
+
- **Automatic Token Management**: Handle OIDC authentication callbacks and token processing
|
|
12
|
+
- **React Components**: Provide ready-to-use React components for OIDC integration
|
|
13
|
+
- **Server Communication**: Coordinate with server-side OIDC relying party implementation
|
|
14
|
+
- **Authentication State Management**: Manage authentication state across the application
|
|
15
|
+
|
|
16
|
+
## Key Concepts
|
|
17
|
+
|
|
18
|
+
### Web-based OIDC Relying Party
|
|
19
|
+
This package implements the client-side portion of OIDC authentication, handling redirects, callbacks, and token management in web browsers.
|
|
20
|
+
|
|
21
|
+
### Flow-based Authentication
|
|
22
|
+
Integrates with the OwlMeans flow system to support complex authentication workflows that may involve multiple steps and decision points.
|
|
23
|
+
|
|
24
|
+
### Server-Client Coordination
|
|
25
|
+
Works in tandem with `@owlmeans/server-oidc-rp` to provide a complete OIDC solution where the server handles sensitive operations and the client manages user interactions.
|
|
26
|
+
|
|
27
|
+
### Authentication Dispatcher
|
|
28
|
+
Provides a dispatcher component that automatically handles OIDC callbacks and coordinates authentication state.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @owlmeans/web-oidc-rp
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## API Reference
|
|
37
|
+
|
|
38
|
+
### Types
|
|
39
|
+
|
|
40
|
+
#### `OidcAuthService`
|
|
41
|
+
Main service interface for web-based OIDC operations.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
interface OidcAuthService extends InitializedService {
|
|
45
|
+
dispatch(params: Record<string, string>): Promise<boolean>
|
|
46
|
+
authenticate(flow: FlowModel, params: OIDCAuthInitParams): Promise<string | null>
|
|
47
|
+
proceedToRedirectUrl(extras: OidcAuthRedirectExtras): Promise<string>
|
|
48
|
+
dispatchClientOnly(): Promise<void>
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### `OidcAuthRedirectExtras`
|
|
53
|
+
Configuration for OIDC redirect operations.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
interface OidcAuthRedirectExtras extends FlowPayload {
|
|
57
|
+
purpose: OidcAuthPurposes
|
|
58
|
+
uid?: string
|
|
59
|
+
alias?: string
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### `OidcInteraction`
|
|
64
|
+
Resource record for storing OIDC interaction data.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
interface OidcInteraction extends ResourceRecord {
|
|
68
|
+
authUrl: string
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### `Config`
|
|
73
|
+
Configuration interface extending AppConfig.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
interface Config extends AppConfig, WithSharedConfig { }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### `Context`
|
|
80
|
+
Context interface for web applications.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
interface Context<C extends Config = Config> extends AppContext<C> { }
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Enums
|
|
87
|
+
|
|
88
|
+
#### `OidcAuthPurposes`
|
|
89
|
+
Enumeration of OIDC authentication purposes.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
enum OidcAuthPurposes {
|
|
93
|
+
Unknown = 'unknown',
|
|
94
|
+
Subscribe = 'subscribe',
|
|
95
|
+
Login = 'login'
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Factory Functions
|
|
100
|
+
|
|
101
|
+
#### `makeOidcAuthService(alias?: string): OidcAuthService`
|
|
102
|
+
|
|
103
|
+
Creates an OIDC authentication service instance for web applications.
|
|
104
|
+
|
|
105
|
+
**Parameters:**
|
|
106
|
+
- `alias`: Service alias (defaults to 'oidc-rp')
|
|
107
|
+
|
|
108
|
+
**Returns:** OidcAuthService instance
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { makeOidcAuthService } from '@owlmeans/web-oidc-rp'
|
|
112
|
+
|
|
113
|
+
const oidcService = makeOidcAuthService('web-oidc-auth')
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### React Components
|
|
117
|
+
|
|
118
|
+
#### `Dispatcher`
|
|
119
|
+
Higher-order component that handles OIDC authentication callbacks and token processing.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const Dispatcher = DispatcherHOC(({ provideToken, navigate }) => {
|
|
123
|
+
// Component implementation handles OIDC callback processing
|
|
124
|
+
})
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Props:**
|
|
128
|
+
- `provideToken`: Function to provide authentication tokens
|
|
129
|
+
- `navigate`: Function to handle navigation after authentication
|
|
130
|
+
|
|
131
|
+
### Constants
|
|
132
|
+
|
|
133
|
+
#### Service Configuration
|
|
134
|
+
```typescript
|
|
135
|
+
const DEFAULT_ALIAS = 'oidc-rp'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Usage Examples
|
|
139
|
+
|
|
140
|
+
### Basic Setup
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { makeOidcAuthService } from '@owlmeans/web-oidc-rp'
|
|
144
|
+
import { makeAppContext, makeAppConfig } from '@owlmeans/web-client'
|
|
145
|
+
|
|
146
|
+
// Create app context with OIDC configuration
|
|
147
|
+
const config = makeAppConfig('web-app', {
|
|
148
|
+
oidc: {
|
|
149
|
+
// OIDC configuration shared with server
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const context = makeAppContext(config)
|
|
154
|
+
|
|
155
|
+
// Create and register OIDC service
|
|
156
|
+
const oidcService = makeOidcAuthService()
|
|
157
|
+
context.registerService(oidcService)
|
|
158
|
+
|
|
159
|
+
// Initialize context
|
|
160
|
+
await context.configure().init()
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### React Application Integration
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import React from 'react'
|
|
167
|
+
import { BrowserRouter, Routes, Route } from 'react-router-dom'
|
|
168
|
+
import { Dispatcher } from '@owlmeans/web-oidc-rp'
|
|
169
|
+
|
|
170
|
+
function App() {
|
|
171
|
+
return (
|
|
172
|
+
<BrowserRouter>
|
|
173
|
+
<Routes>
|
|
174
|
+
<Route path="/auth/callback" element={<Dispatcher />} />
|
|
175
|
+
<Route path="/login" element={<LoginPage />} />
|
|
176
|
+
<Route path="/dashboard" element={<Dashboard />} />
|
|
177
|
+
</Routes>
|
|
178
|
+
</BrowserRouter>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function LoginPage() {
|
|
183
|
+
const context = useContext()
|
|
184
|
+
const oidcService = context.service<OidcAuthService>('oidc-rp')
|
|
185
|
+
|
|
186
|
+
const handleOidcLogin = async () => {
|
|
187
|
+
const flow = await context.service<FlowService>('flow').createFlow('oidc-auth')
|
|
188
|
+
const redirectUrl = await oidcService.authenticate(flow, {
|
|
189
|
+
entity: 'my-organization',
|
|
190
|
+
profile: 'user-profile'
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
if (redirectUrl) {
|
|
194
|
+
window.location.href = redirectUrl
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<div>
|
|
200
|
+
<button onClick={handleOidcLogin}>
|
|
201
|
+
Login with OIDC
|
|
202
|
+
</button>
|
|
203
|
+
</div>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### OIDC Authentication Flow
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { useFlow } from '@owlmeans/web-flow'
|
|
212
|
+
import { useContext } from '@owlmeans/web-client'
|
|
213
|
+
import { OidcAuthService } from '@owlmeans/web-oidc-rp'
|
|
214
|
+
|
|
215
|
+
function useOidcAuth() {
|
|
216
|
+
const context = useContext()
|
|
217
|
+
const flowClient = useFlow()
|
|
218
|
+
const oidcService = context.service<OidcAuthService>('oidc-rp')
|
|
219
|
+
|
|
220
|
+
const initiateAuth = async (entityId: string, profileId?: string) => {
|
|
221
|
+
if (!flowClient) return null
|
|
222
|
+
|
|
223
|
+
const flow = flowClient.flow()
|
|
224
|
+
const redirectUrl = await oidcService.authenticate(flow, {
|
|
225
|
+
entity: entityId,
|
|
226
|
+
profile: profileId
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
return redirectUrl
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const handleCallback = async (urlParams: URLSearchParams) => {
|
|
233
|
+
const params: Record<string, string> = {}
|
|
234
|
+
urlParams.forEach((value, key) => {
|
|
235
|
+
params[key] = value
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
return await oidcService.dispatch(params)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { initiateAuth, handleCallback }
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Custom Dispatcher Implementation
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { useSearchParams } from 'react-router-dom'
|
|
249
|
+
import { useEffect, useState } from 'react'
|
|
250
|
+
import { useContext } from '@owlmeans/web-client'
|
|
251
|
+
import { OidcAuthService } from '@owlmeans/web-oidc-rp'
|
|
252
|
+
|
|
253
|
+
function CustomOidcDispatcher() {
|
|
254
|
+
const [searchParams] = useSearchParams()
|
|
255
|
+
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading')
|
|
256
|
+
const context = useContext()
|
|
257
|
+
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
const processCallback = async () => {
|
|
260
|
+
try {
|
|
261
|
+
const oidcService = context.service<OidcAuthService>('oidc-rp')
|
|
262
|
+
|
|
263
|
+
const params: Record<string, string> = {}
|
|
264
|
+
searchParams.forEach((value, key) => {
|
|
265
|
+
params[key] = value
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
const success = await oidcService.dispatch(params)
|
|
269
|
+
|
|
270
|
+
if (success) {
|
|
271
|
+
setStatus('success')
|
|
272
|
+
// Redirect to intended destination
|
|
273
|
+
window.location.href = '/dashboard'
|
|
274
|
+
} else {
|
|
275
|
+
setStatus('error')
|
|
276
|
+
}
|
|
277
|
+
} catch (error) {
|
|
278
|
+
console.error('OIDC callback processing failed:', error)
|
|
279
|
+
setStatus('error')
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
processCallback()
|
|
284
|
+
}, [searchParams])
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<div>
|
|
288
|
+
{status === 'loading' && <div>Processing authentication...</div>}
|
|
289
|
+
{status === 'success' && <div>Authentication successful, redirecting...</div>}
|
|
290
|
+
{status === 'error' && <div>Authentication failed. Please try again.</div>}
|
|
291
|
+
</div>
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Flow-based Authentication
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
import { FlowModel } from '@owlmeans/flow'
|
|
300
|
+
import { OIDCAuthInitParams } from '@owlmeans/oidc'
|
|
301
|
+
|
|
302
|
+
function FlowBasedAuth() {
|
|
303
|
+
const context = useContext()
|
|
304
|
+
const oidcService = context.service<OidcAuthService>('oidc-rp')
|
|
305
|
+
|
|
306
|
+
const startAuthFlow = async () => {
|
|
307
|
+
// Create or get current flow
|
|
308
|
+
const flowService = context.service<FlowService>('flow')
|
|
309
|
+
const flow = await flowService.createFlow('oidc-authentication')
|
|
310
|
+
|
|
311
|
+
// Configure OIDC parameters
|
|
312
|
+
const params: OIDCAuthInitParams = {
|
|
313
|
+
entity: 'my-organization',
|
|
314
|
+
profile: 'default',
|
|
315
|
+
purpose: 'login'
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Start authentication
|
|
319
|
+
const redirectUrl = await oidcService.authenticate(flow, params)
|
|
320
|
+
|
|
321
|
+
if (redirectUrl) {
|
|
322
|
+
// Store flow state before redirect
|
|
323
|
+
await flowService.persistFlow()
|
|
324
|
+
|
|
325
|
+
// Redirect to OIDC provider
|
|
326
|
+
window.location.href = redirectUrl
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return (
|
|
331
|
+
<button onClick={startAuthFlow}>
|
|
332
|
+
Start Secure Login Flow
|
|
333
|
+
</button>
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Advanced Redirect Handling
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import { OidcAuthRedirectExtras, OidcAuthPurposes } from '@owlmeans/web-oidc-rp'
|
|
342
|
+
|
|
343
|
+
async function handleComplexRedirect(
|
|
344
|
+
oidcService: OidcAuthService,
|
|
345
|
+
purpose: OidcAuthPurposes,
|
|
346
|
+
userId?: string
|
|
347
|
+
) {
|
|
348
|
+
const extras: OidcAuthRedirectExtras = {
|
|
349
|
+
purpose,
|
|
350
|
+
uid: userId,
|
|
351
|
+
alias: 'dashboard'
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
const finalUrl = await oidcService.proceedToRedirectUrl(extras)
|
|
356
|
+
window.location.href = finalUrl
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.error('Redirect processing failed:', error)
|
|
359
|
+
// Handle error appropriately
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Integration with Authentication Guards
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
import { useAuth } from '@owlmeans/client-auth'
|
|
368
|
+
import { Navigate } from 'react-router-dom'
|
|
369
|
+
|
|
370
|
+
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
|
371
|
+
const auth = useAuth()
|
|
372
|
+
const context = useContext()
|
|
373
|
+
const oidcService = context.service<OidcAuthService>('oidc-rp')
|
|
374
|
+
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
const checkAuth = async () => {
|
|
377
|
+
const token = await auth.authenticated()
|
|
378
|
+
if (!token) {
|
|
379
|
+
// Initiate OIDC authentication
|
|
380
|
+
const flow = await context.service<FlowService>('flow').createFlow('auth')
|
|
381
|
+
const redirectUrl = await oidcService.authenticate(flow, {
|
|
382
|
+
entity: 'default'
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
if (redirectUrl) {
|
|
386
|
+
window.location.href = redirectUrl
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
checkAuth()
|
|
392
|
+
}, [])
|
|
393
|
+
|
|
394
|
+
const isAuthenticated = auth.isAuthenticated()
|
|
395
|
+
|
|
396
|
+
if (!isAuthenticated) {
|
|
397
|
+
return <Navigate to="/login" replace />
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return <>{children}</>
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Error Handling
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import { useEffect, useState } from 'react'
|
|
408
|
+
import { OidcAuthService } from '@owlmeans/web-oidc-rp'
|
|
409
|
+
|
|
410
|
+
function useOidcErrorHandling() {
|
|
411
|
+
const [error, setError] = useState<string | null>(null)
|
|
412
|
+
const context = useContext()
|
|
413
|
+
|
|
414
|
+
const handleOidcOperation = async (operation: () => Promise<any>) => {
|
|
415
|
+
try {
|
|
416
|
+
setError(null)
|
|
417
|
+
return await operation()
|
|
418
|
+
} catch (err) {
|
|
419
|
+
const errorMessage = err instanceof Error ? err.message : 'OIDC operation failed'
|
|
420
|
+
setError(errorMessage)
|
|
421
|
+
|
|
422
|
+
// Log error for debugging
|
|
423
|
+
console.error('OIDC Error:', err)
|
|
424
|
+
|
|
425
|
+
// Could also send to error reporting service
|
|
426
|
+
return null
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return { error, handleOidcOperation }
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Integration Patterns
|
|
435
|
+
|
|
436
|
+
### Routing Integration
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
import { Routes, Route } from 'react-router-dom'
|
|
440
|
+
import { Dispatcher } from '@owlmeans/web-oidc-rp'
|
|
441
|
+
|
|
442
|
+
function AppRoutes() {
|
|
443
|
+
return (
|
|
444
|
+
<Routes>
|
|
445
|
+
{/* OIDC callback route */}
|
|
446
|
+
<Route path="/auth/oidc/callback" element={<Dispatcher />} />
|
|
447
|
+
|
|
448
|
+
{/* Other application routes */}
|
|
449
|
+
<Route path="/" element={<HomePage />} />
|
|
450
|
+
<Route path="/dashboard" element={<ProtectedDashboard />} />
|
|
451
|
+
</Routes>
|
|
452
|
+
)
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### State Management Integration
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
import { createContext, useContext as useReactContext } from 'react'
|
|
460
|
+
import { OidcAuthService } from '@owlmeans/web-oidc-rp'
|
|
461
|
+
|
|
462
|
+
const OidcContext = createContext<OidcAuthService | null>(null)
|
|
463
|
+
|
|
464
|
+
export function OidcProvider({ children }: { children: React.ReactNode }) {
|
|
465
|
+
const context = useContext()
|
|
466
|
+
const oidcService = context.service<OidcAuthService>('oidc-rp')
|
|
467
|
+
|
|
468
|
+
return (
|
|
469
|
+
<OidcContext.Provider value={oidcService}>
|
|
470
|
+
{children}
|
|
471
|
+
</OidcContext.Provider>
|
|
472
|
+
)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export function useOidc() {
|
|
476
|
+
const oidcService = useReactContext(OidcContext)
|
|
477
|
+
if (!oidcService) {
|
|
478
|
+
throw new Error('useOidc must be used within OidcProvider')
|
|
479
|
+
}
|
|
480
|
+
return oidcService
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
## Best Practices
|
|
485
|
+
|
|
486
|
+
1. **Secure Redirects**: Always validate redirect URLs and use HTTPS in production
|
|
487
|
+
2. **State Management**: Properly manage authentication state across page reloads
|
|
488
|
+
3. **Error Handling**: Implement comprehensive error handling for network and authentication failures
|
|
489
|
+
4. **Flow Integration**: Use flows for complex authentication scenarios
|
|
490
|
+
5. **Token Security**: Never expose sensitive tokens in client-side storage
|
|
491
|
+
6. **PKCE Support**: Use PKCE (Proof Key for Code Exchange) for enhanced security
|
|
492
|
+
7. **Callback Validation**: Always validate OIDC callback parameters before processing
|
|
493
|
+
|
|
494
|
+
## Dependencies
|
|
495
|
+
|
|
496
|
+
This package depends on:
|
|
497
|
+
- `@owlmeans/auth` - Authentication framework
|
|
498
|
+
- `@owlmeans/client` - Client framework
|
|
499
|
+
- `@owlmeans/client-auth` - Client authentication
|
|
500
|
+
- `@owlmeans/client-flow` - Client flow management
|
|
501
|
+
- `@owlmeans/flow` - Flow system
|
|
502
|
+
- `@owlmeans/oidc` - Core OIDC functionality
|
|
503
|
+
- `@owlmeans/web-client` - Web client framework
|
|
504
|
+
- `@owlmeans/web-flow` - Web flow integration
|
|
505
|
+
- `oidc-client-ts` - TypeScript OIDC client library
|
|
506
|
+
- `react` - React framework
|
|
507
|
+
- `react-router-dom` - React routing
|
|
508
|
+
|
|
509
|
+
## Related Packages
|
|
510
|
+
|
|
511
|
+
- [`@owlmeans/server-oidc-rp`](../server-oidc-rp) - Server-side OIDC Relying Party
|
|
512
|
+
- [`@owlmeans/oidc`](../oidc) - Core OIDC functionality
|
|
513
|
+
- [`@owlmeans/client-auth`](../client-auth) - Client authentication framework
|
|
514
|
+
- [`@owlmeans/web-client`](../web-client) - Web client framework
|
|
515
|
+
- [`@owlmeans/web-flow`](../web-flow) - Web flow management
|
package/build/.gitkeep
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/auth/plugins/index.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,OAAO;;CAAc,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { plugins as authPlugins } from '@owlmeans/client-auth/manager';
|
|
2
|
+
import { OIDC_CLIENT_AUTH } from '@owlmeans/oidc';
|
|
3
|
+
import { oidcClientPlugin } from './oidc-client.js';
|
|
4
|
+
authPlugins[OIDC_CLIENT_AUTH] = oidcClientPlugin;
|
|
5
|
+
export const plugins = authPlugins;
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/auth/plugins/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,+BAA+B,CAAA;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAEnD,WAAW,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAA;AAEhD,MAAM,CAAC,MAAM,OAAO,GAAG,WAAW,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-client.d.ts","sourceRoot":"","sources":["../../../src/auth/plugins/oidc-client.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAA;AAajF,eAAO,MAAM,gBAAgB,EAAE,oBAmG9B,CAAA"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { AUTH_SCOPE, AuthenticationStage, AuthManagerError, AuthRole } from '@owlmeans/auth';
|
|
3
|
+
import { OIDC_CLIENT_AUTH } from '@owlmeans/oidc';
|
|
4
|
+
import { useEffect } from 'react';
|
|
5
|
+
import LinearProgress from '@mui/material/LinearProgress';
|
|
6
|
+
import { EnvelopeKind, makeEnvelopeModel } from '@owlmeans/basic-envelope';
|
|
7
|
+
import { useContext } from '@owlmeans/web-client';
|
|
8
|
+
import { DEFAULT_ALIAS, OidcAuthPurposes } from '../../consts.js';
|
|
9
|
+
import { OidcAuthStep, OidpAuthStep, UnknownFlowStep } from '@owlmeans/flow';
|
|
10
|
+
import { useModule } from '@owlmeans/client';
|
|
11
|
+
export const oidcClientPlugin = {
|
|
12
|
+
type: OIDC_CLIENT_AUTH,
|
|
13
|
+
Implementation: Renderer => ({ type, stage, control }) => {
|
|
14
|
+
const context = useContext();
|
|
15
|
+
Renderer = Renderer ?? oidcClientPlugin.Renderer;
|
|
16
|
+
const module = useModule();
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
let canceled = false;
|
|
19
|
+
// @TODO extract somewhere else and make it more consequential
|
|
20
|
+
// This way it looks too messy
|
|
21
|
+
switch (control.stage) {
|
|
22
|
+
case AuthenticationStage.Init:
|
|
23
|
+
control.hasPersistentState().then(async (hasState) => {
|
|
24
|
+
if (canceled) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// 3. Properly process additional paramters on sequential rendering to restore control state
|
|
28
|
+
if (hasState) {
|
|
29
|
+
await control.restore();
|
|
30
|
+
await control.cleanUpState();
|
|
31
|
+
const flow = await control.flow();
|
|
32
|
+
if (flow != null) {
|
|
33
|
+
const state = await flow.state();
|
|
34
|
+
if ([OidcAuthStep.PostAuthen, OidpAuthStep.PostAuth].includes(state?.step().step)) {
|
|
35
|
+
const url = new URL(window.location.href);
|
|
36
|
+
const auth = {
|
|
37
|
+
...control.allowance,
|
|
38
|
+
type,
|
|
39
|
+
challenge: control.allowance?.challenge ?? '',
|
|
40
|
+
credential: url.searchParams.toString(),
|
|
41
|
+
role: AuthRole.User,
|
|
42
|
+
userId: 'code',
|
|
43
|
+
scopes: [AUTH_SCOPE]
|
|
44
|
+
};
|
|
45
|
+
const tokenized = await control.authenticate(auth);
|
|
46
|
+
// @TODO Introduce some standard further flow in case this plugin/component
|
|
47
|
+
// is used without a callback
|
|
48
|
+
if (tokenized.token !== '') {
|
|
49
|
+
throw new AuthManagerError('callback');
|
|
50
|
+
}
|
|
51
|
+
return; // @TODO Will it work here?
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// return
|
|
55
|
+
}
|
|
56
|
+
// 1. Initialize OIDC server side authentication
|
|
57
|
+
const oidc = context.service(DEFAULT_ALIAS);
|
|
58
|
+
// @TODO we need to provide an identity provider client as entityId here for flexibel usage
|
|
59
|
+
const _source = await oidc.proceedToRedirectUrl({
|
|
60
|
+
purpose: control.source ?? OidcAuthPurposes.Unknown,
|
|
61
|
+
uid: 'uid' in module.params ? `${module.params.uid}` : undefined,
|
|
62
|
+
alias: module.alias
|
|
63
|
+
});
|
|
64
|
+
const source = new URL(_source);
|
|
65
|
+
source.search = '';
|
|
66
|
+
await control.requestAllowence({ type, source: source.toString() });
|
|
67
|
+
});
|
|
68
|
+
case AuthenticationStage.Authenticate:
|
|
69
|
+
control.flow().then(async (flow) => {
|
|
70
|
+
if (canceled) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const flowState = await flow?.state();
|
|
74
|
+
if (flowState == null) {
|
|
75
|
+
throw new UnknownFlowStep(AuthenticationStage.Authenticate);
|
|
76
|
+
}
|
|
77
|
+
if (control.allowance?.challenge != null) {
|
|
78
|
+
const envelope = makeEnvelopeModel(control.allowance?.challenge, EnvelopeKind.Wrap);
|
|
79
|
+
const challenge = envelope.message(true);
|
|
80
|
+
const parts = challenge.split(':http');
|
|
81
|
+
const url = 'http' + parts[1];
|
|
82
|
+
// 2. Include additional parameters (control state with allowance to flow state)
|
|
83
|
+
await control.persist();
|
|
84
|
+
document.location.href = url;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return () => { canceled = true; };
|
|
89
|
+
}, [type, stage]);
|
|
90
|
+
if (Renderer == null) {
|
|
91
|
+
throw new SyntaxError('Renderer is not defined for OIDC plugin');
|
|
92
|
+
}
|
|
93
|
+
return _jsx(Renderer, { type: type, stage: stage, control: control, params: module.params });
|
|
94
|
+
},
|
|
95
|
+
Renderer: () => _jsx(LinearProgress, {})
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=oidc-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-client.js","sourceRoot":"","sources":["../../../src/auth/plugins/oidc-client.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/components/dispatcher.tsx"],"names":[],"mappings":"AAWA,eAAO,MAAM,UAAU,6FAiDrB,CAAA"}
|