You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The application uses different strategies to protect routes depending on the component type (Server vs. Client) and the specific requirements of the page.
312
312
313
-
```typescript
314
-
import { auth } from'@/auth';
313
+
#### Server components
314
+
315
+
In Server Components, use the `auth()` function to retrieve the current session. If the session is missing, you can redirect the user to the login page.
316
+
317
+
```typescript title="protecting a Server Component"
318
+
import { auth, signIn } from'@/auth';
315
319
316
320
exportdefaultasyncfunction ProtectedPage() {
317
321
const session =awaitauth();
318
322
319
323
if (!session) {
320
-
redirect('/login');
324
+
// Option 1: Trigger sign-in flow
325
+
returnawaitsignIn();
326
+
327
+
// Option 2: Redirect to login page
328
+
// redirect('/login');
321
329
}
322
330
323
331
// Render protected content
324
332
}
325
333
```
326
334
327
-
### Role-Based Access Control
335
+
#### Handling authentication errors
336
+
337
+
When making API calls via the SDK, you should handle authentication errors (like `401 Unauthorized` or token expiration). If the SDK returns a `401`, it usually means the session has expired or the token is invalid.
338
+
339
+
```typescript title="handling 401 and token errors"
340
+
try {
341
+
const data =awaitsdk.modules.getPage({ slug }, {}, session?.accessToken);
342
+
} catch (error) {
343
+
if (error?.status===401) {
344
+
if (!session?.user) {
345
+
// User is not logged in, trigger sign-in
346
+
returnawaitsignIn();
347
+
} else {
348
+
// User is logged in but unauthorized for this page
349
+
notFound();
350
+
}
351
+
}
352
+
}
353
+
```
328
354
329
-
Check user roles to control access to features:
355
+
Additionally, if the session contains a `RefreshTokenError`, you should force the user to re-authenticate:
356
+
357
+
```typescript
358
+
if (session?.error==='RefreshTokenError') {
359
+
returnawaitsignIn();
360
+
}
361
+
```
362
+
363
+
#### Client components
364
+
365
+
In Client Components, use the `useSession` hook. Note that `useSession` provides a `status` field (`loading`, `authenticated`, `unauthenticated`) which you can use to protect the UI.
366
+
367
+
```typescript title="protecting a Client Component"
368
+
'use client';
369
+
import { useSession } from'next-auth/react';
370
+
371
+
exportdefaultfunction ClientPage() {
372
+
const { data: session, status } =useSession();
373
+
374
+
if (status==='loading') return <Spinner />;
375
+
if (status==='unauthenticated') return <RedirectToLogin />;
376
+
377
+
return <div>Welcome, {session.user.name}</div>;
378
+
}
379
+
```
380
+
381
+
### Role-based access control
382
+
383
+
O2S supports role-based access control (RBAC) at both the user level and the organization (customer) level.
384
+
385
+
#### User-level roles
386
+
387
+
The `session` object includes a top-level `role` for the user, which typically represents their global system role.
330
388
331
389
```typescript
332
390
if (session?.user?.role==='selfservice_admin') {
333
-
// Show admin features
391
+
// Show global admin features
392
+
}
393
+
```
394
+
395
+
#### Organization-level roles
396
+
397
+
In B2B scenarios, a user might have different roles depending on the current customer context. These are stored within the `customer` object in the session.
// Show features specific to organization managers
404
+
}
405
+
```
406
+
407
+
#### Granular permissions
408
+
409
+
While roles provide a broad check, O2S encourages using **permission-based access control** for specific features and blocks. Permissions are typically part of the data returned by the SDK for a specific block or module.
410
+
411
+
```typescript
412
+
// Example: Checking a specific permission within a block
413
+
if (blockData.permissions?.canEdit) {
414
+
return <EditButton />;
334
415
}
335
416
```
336
417
418
+
For more details on how the API Harmonization server determines these permissions based on the user's roles, see the [API Harmonization authentication documentation](../harmonization-app/authentication.md).
419
+
337
420
### Permission-based access control in blocks
338
421
339
422
Blocks can include permission flags in their responses that indicate what actions the user can perform. The frontend uses these flags to conditionally render features.
@@ -401,34 +484,98 @@ This pattern allows you to:
401
484
402
485
The permission flags come from the API Harmonization server, which checks the user's permissions from their JWT token. For more details on how permissions are checked and enforced, see the [API Harmonization authentication documentation](../harmonization-app/authentication.md).
403
486
404
-
### Customer Context Switching
487
+
### Customer context switching
405
488
406
-
To implement customer switching:
489
+
For B2B scenarios, users can be associated with multiple customer accounts (organizations) and switch between them. To implement this in the frontend, you should use the `updateOrganization` facade.
407
490
408
-
```typescript
409
-
// Update session with new customer context
410
-
awaitupdate({
411
-
customerId: selectedCustomerId,
412
-
});
491
+
Instead of calling session updates directly in your components, O2S provides a facade in `src/auth/auth.organizations.ts`. This allows the UI to remain decoupled from the specific IAM implementation being used.
// This call triggers the integration-specific update logic
498
+
awaitupdateOrganization(session, customer);
499
+
};
413
500
```
414
501
415
-
## Extending Authentication
502
+
The `updateOrganization` utility is a wrapper around the current IAM integration's implementation. For example, in the mocked integration, it is located at `packages/integrations/mocked/src/auth/auth.updateOrganization.ts`.
503
+
504
+
A typical implementation follows these steps:
505
+
506
+
1. Update user context by making an API call to your IAM or backend to update the user's active organization.
507
+
2. Use `session.update()` to refresh the NextAuth session with the new roles and permissions.
508
+
3. Refresh app state which usually involves a `window.location.reload()` or a redirect to ensure all hooks and SDK instances are re-initialized with the new context.
// 2. Refresh to apply changes across the application
518
+
window.location.reload();
519
+
}
520
+
```
521
+
522
+
In a production environment with a real IAM system, this function might first perform an asynchronous request to exchange the current token for one scoped to the selected organization before updating the session.
523
+
524
+
The following example is inspired by the `ContextSwitcher` component, showing how to handle the organization selection:
2. Add provider configuration to `auth.providers.ts`
423
-
3. Update UI to include the new sign-in option
538
+
// Use the facade to handle the update
539
+
awaitupdateOrganization(session, customer);
540
+
} catch (error) {
541
+
console.error('Failed to update organization:', error);
542
+
} finally {
543
+
spinner.toggle(false);
544
+
}
545
+
};
546
+
```
547
+
548
+
### Adding new providers
549
+
550
+
O2S supports any authentication provider compatible with [Auth.js](https://authjs.dev/reference/core/providers).
551
+
552
+
Standard OAuth providers (like GitHub, Google, Azure AD) or the Credentials provider are included in the `next-auth` package. You can then add the new provider to the `providers` array in `apps/frontend/src/auth/auth.providers.ts`. The application automatically processes this array to generate the login UI.
role: process.env.AUTH_DEFAULT_USER_ROLE, // Assign default O2S role
568
+
};
569
+
},
570
+
}),
571
+
];
572
+
```
424
573
425
-
### Custom User Data
574
+
The login page uses the `providerMap` (defined in the same file) to dynamically render sign-in buttons for all configured OAuth providers. Once added to the `providers` array, the new option will automatically appear on the `/login` page (unless it is a `credentials` provider which is handled separately).
426
575
427
-
To store additional user data:
576
+
#### Delegating the login page
428
577
429
-
1. Extend the Prisma User model
430
-
2. Update the JWT and Session type definitions
431
-
3. Modify the JWT callback to include the additional data
578
+
It is also possible to completely delegate the login page to your IAM system (e.g., use login pages provided directly by Keycloak or Azure AD). To do this, simply configure `auth.providers.ts` to **NOT** include the `Credentials` provider. When no `Credentials` provider is present, you can configure your application to redirect users directly to the IAM's login page instead of using the O2S built-in login screen.
0 commit comments