AuthZ Client¶
SentinelAuthz is the browser auth client for authz mode. It manages dual tokens: an IdP token (identity, from Google/EntraID) and a Sentinel authz token (authorization).
Setup¶
import { SentinelAuthz, IdpConfigs } from '@sentinel-auth/js'
const authz = new SentinelAuthz({
sentinelUrl: 'http://localhost:9003',
idps: { google: IdpConfigs.google('your-google-client-id') },
})
| Option | Type | Default | Description |
|---|---|---|---|
sentinelUrl |
string |
required | Base URL of the Sentinel service |
idps |
Record<string, IdpConfig> |
{} |
IdP configs keyed by provider name |
redirectUri |
string |
${origin}/auth/callback |
OAuth redirect URI |
storage |
AuthzTokenStore |
AuthzMemoryStore |
Token storage backend |
autoRefresh |
boolean |
true |
Refresh authz token before expiry |
refreshBuffer |
number |
30 |
Seconds before expiry to trigger refresh |
Built-in IdP helpers: IdpConfigs.google(clientId), IdpConfigs.entraId(clientId, tenantId). Pass a custom IdpConfig object for other providers.
How it works¶
1. authz.login('google') -> redirect to Google
2. Google redirects back with #id_token=...
3. authz.handleCallback() -> extract token, verify nonce
4. authz.resolve(idpToken, provider) -> POST /authz/resolve, get workspaces
5. authz.selectWorkspace(...) -> POST /authz/resolve with workspace_id
6. Both tokens stored, auto-refresh scheduled
Methods¶
login(provider)¶
Redirect to the IdP's authorization page. Provider must be configured in idps.
handleCallback()¶
Extract id_token from URL hash after IdP redirect. Verifies nonce. Returns null if no hash present.
resolve(idpToken, provider)¶
Validate IdP token with Sentinel, discover workspaces.
const result = await authz.resolve(idpToken, 'google')
// result.user -> { id, email, name }
// result.workspaces -> [{ id, name, slug, role }]
selectWorkspace(idpToken, provider, workspaceId)¶
Exchange IdP token for a Sentinel authz token scoped to a workspace.
getUser() / isAuthenticated¶
const user = authz.getUser()
// { userId, email, name, workspaceId, workspaceSlug, workspaceRole, groups }
if (authz.isAuthenticated) { /* ... */ }
getHeaders()¶
fetch / fetchJson¶
Inject dual-token headers. On 401, refresh and retry once.
const res = await authz.fetch('/api/notes')
const notes = await authz.fetchJson<Note[]>('/api/notes')
onAuthStateChange(cb) / logout() / destroy()¶
const unsub = authz.onAuthStateChange((user) => { /* ... */ })
authz.logout() // clear tokens, notify listeners
authz.destroy() // clean up timers
Token storage¶
| Backend | Persistence |
|---|---|
AuthzMemoryStore (default) |
Lost on page refresh |
AuthzLocalStorageStore |
Survives refresh, shared across tabs |
import { SentinelAuthz, AuthzLocalStorageStore } from '@sentinel-auth/js'
const authz = new SentinelAuthz({
sentinelUrl: '...', storage: new AuthzLocalStorageStore(),
})
Complete example¶
import { SentinelAuthz, IdpConfigs, AuthzLocalStorageStore } from '@sentinel-auth/js'
const authz = new SentinelAuthz({
sentinelUrl: 'http://localhost:9003',
idps: { google: IdpConfigs.google('your-client-id') },
storage: new AuthzLocalStorageStore(),
})
// Login page
authz.login('google')
// Callback page (/auth/callback)
const cb = authz.handleCallback()
if (cb) {
const result = await authz.resolve(cb.idpToken, cb.provider)
if (result.workspaces?.length === 1) {
await authz.selectWorkspace(cb.idpToken, cb.provider, result.workspaces[0].id)
window.location.href = '/dashboard'
}
}
// After auth
const notes = await authz.fetchJson<Note[]>('/api/notes')
AuthZ vs Proxy mode¶
AuthZ (SentinelAuthz) |
Proxy (SentinelAuth) |
|
|---|---|---|
| IdP interaction | You configure IdPs, SDK redirects | Sentinel manages redirect flow |
| Tokens stored | IdP token + authz token | Access + refresh token |
| Headers sent | Authorization + X-Authz-Token |
Authorization only |
| PKCE | Not needed (implicit flow) | Generated by SDK |