@hyper.authio/bff 0.0.0-stage → 0.7.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/README.md +236 -2
- package/dist/index.cjs +956 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +327 -0
- package/dist/index.d.ts +327 -0
- package/dist/index.js +950 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -4
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { RequestHandler, Request, Response } from 'express';
|
|
2
|
+
import { AuthioClock, AuthioClientOptions, AuthioClient, AuthioPrompt } from '@hyper.authio/client';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A session as the store keeps it. `data` is encrypted by the BFF (AES-256-GCM, from `cookieSecret`): the store never
|
|
6
|
+
* sees the tokens. `sessionId` and `subject` are kept in clear so back-channel logout can find the sessions to end.
|
|
7
|
+
*/
|
|
8
|
+
interface AuthioBffStoredSession {
|
|
9
|
+
/** The store key: `sessionKeyPrefix` followed by 64 random hex characters. The cookie carries the part after the prefix. */
|
|
10
|
+
readonly key: string;
|
|
11
|
+
/** The Authio session (`sid` of the ID token), when Authio named one. */
|
|
12
|
+
readonly sessionId: string | undefined;
|
|
13
|
+
/** The person (`sub`). */
|
|
14
|
+
readonly subject: string | undefined;
|
|
15
|
+
/** When the session expires, in milliseconds since the epoch; a store may drop it from then on. */
|
|
16
|
+
readonly expiresAt: number;
|
|
17
|
+
/** The encrypted session. */
|
|
18
|
+
readonly data: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Where the BFF keeps its sessions. The default is in memory (one process); implement this over Redis or a database for
|
|
22
|
+
* several instances, indexing each session by `sessionId` and `subject` so back-channel logout can end them.
|
|
23
|
+
* Every method may return a value or a promise.
|
|
24
|
+
*/
|
|
25
|
+
interface AuthioBffSessionStore {
|
|
26
|
+
/** Reads a session; `undefined` when it is unknown or expired. */
|
|
27
|
+
get(key: string): AuthioBffStoredSession | undefined | Promise<AuthioBffStoredSession | undefined>;
|
|
28
|
+
/** Creates or replaces a session, kept until `expiresAt`. */
|
|
29
|
+
set(session: AuthioBffStoredSession): void | Promise<void>;
|
|
30
|
+
/** Deletes a session. */
|
|
31
|
+
delete(key: string): void | Promise<void>;
|
|
32
|
+
/** Deletes every session of an Authio session (`sid`). */
|
|
33
|
+
deleteBySessionId(sessionId: string): void | Promise<void>;
|
|
34
|
+
/** Deletes every session of a person (`sub`). */
|
|
35
|
+
deleteBySubject(subject: string): void | Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Options of the in-memory store.
|
|
39
|
+
*/
|
|
40
|
+
interface AuthioBffMemoryStoreOptions {
|
|
41
|
+
/** The clock expiry is measured with. Defaults to the system clock. */
|
|
42
|
+
clock?: AuthioClock;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* An in-memory session store, indexed by `sessionId` and `subject`, that drops expired sessions. Sessions live in one
|
|
46
|
+
* process and are lost on restart: use a shared store for several instances.
|
|
47
|
+
*/
|
|
48
|
+
interface AuthioBffMemoryStore extends AuthioBffSessionStore {
|
|
49
|
+
/** The number of sessions held, expired ones included until they are swept. */
|
|
50
|
+
readonly size: number;
|
|
51
|
+
get(key: string): AuthioBffStoredSession | undefined;
|
|
52
|
+
set(session: AuthioBffStoredSession): void;
|
|
53
|
+
delete(key: string): void;
|
|
54
|
+
deleteBySessionId(sessionId: string): void;
|
|
55
|
+
deleteBySubject(subject: string): void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Creates the in-memory session store.
|
|
59
|
+
*/
|
|
60
|
+
declare function createAuthioBffMemoryStore(options?: AuthioBffMemoryStoreOptions): AuthioBffMemoryStore;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Default names and values of the Authio BFF, the same as `Authio.AspNetCore.Bff`'s.
|
|
64
|
+
*/
|
|
65
|
+
declare const AuthioBffDefaults: {
|
|
66
|
+
/** The session cookie name. `__Host-` makes browsers refuse it unless it is `Secure`, has `Path=/` and no `Domain`. */
|
|
67
|
+
readonly CookieName: "__Host-authio";
|
|
68
|
+
/** The route prefix of the BFF endpoints. */
|
|
69
|
+
readonly EndpointPrefix: "/bff";
|
|
70
|
+
/** The anti-forgery header the SPA sends on every API call. */
|
|
71
|
+
readonly AntiForgeryHeaderName: "X-CSRF";
|
|
72
|
+
/** The value the anti-forgery header carries. */
|
|
73
|
+
readonly AntiForgeryHeaderValue: "1";
|
|
74
|
+
/** The redirect path Authio sends the person back to after sign-in. */
|
|
75
|
+
readonly CallbackPath: "/signin-oidc";
|
|
76
|
+
/** The redirect path Authio sends the person back to after sign-out. */
|
|
77
|
+
readonly SignedOutCallbackPath: "/signout-callback-oidc";
|
|
78
|
+
/** The `source` of the message the silent-login page posts to its parent window. */
|
|
79
|
+
readonly SilentLoginMessageSource: "authio-bff-silent-login";
|
|
80
|
+
/** The prefix of the keys sessions are stored under. */
|
|
81
|
+
readonly SessionKeyPrefix: "authio-bff:";
|
|
82
|
+
/** Seconds a session lasts without activity (8 hours). */
|
|
83
|
+
readonly SessionLifetime: number;
|
|
84
|
+
/** Seconds between two checks of the Authio session (5 minutes). */
|
|
85
|
+
readonly SessionValidationInterval: number;
|
|
86
|
+
/** Seconds a sign-in may take between the login request and the callback (15 minutes). */
|
|
87
|
+
readonly LoginLifetime: number;
|
|
88
|
+
/** The scopes asked for at sign-in; `offline_access` gives the BFF a refresh token. */
|
|
89
|
+
readonly Scopes: readonly string[];
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* How the BFF checks that the person's Authio session is still alive: `none`, or `introspection` of the session's
|
|
93
|
+
* refresh token (its access token when there is none) every `sessionValidationInterval`.
|
|
94
|
+
*/
|
|
95
|
+
type AuthioBffSessionValidation = "none" | "introspection";
|
|
96
|
+
/**
|
|
97
|
+
* The `SameSite` mode of the session cookie.
|
|
98
|
+
*/
|
|
99
|
+
type AuthioBffCookieSameSite = "lax" | "strict";
|
|
100
|
+
/**
|
|
101
|
+
* Settings of the Authio BFF. The client itself (`issuer` or `authority` + `realm`, `clientId`, `clientSecret`,
|
|
102
|
+
* `clientAuthentication`, `clientPrivateKey`, `clientEncryptionKey`, `tokenRefreshSkew`…) takes the names of
|
|
103
|
+
* `conformance/options.md`; the BFF's own settings are those of `Authio.AspNetCore.Bff` (`Authio:Bff`).
|
|
104
|
+
*/
|
|
105
|
+
interface AuthioBffOptions extends Partial<AuthioClientOptions> {
|
|
106
|
+
/**
|
|
107
|
+
* An Authio client to use instead of creating one from the connection and credential options above. The BFF still
|
|
108
|
+
* reads `tokenRefreshSkew`, `clientEncryptionKey`, `redirectUri` and `postLogoutRedirectUri` from these options.
|
|
109
|
+
*/
|
|
110
|
+
client?: AuthioClient;
|
|
111
|
+
/**
|
|
112
|
+
* Secrets the session cookie is signed with and the stored sessions and sign-in cookies are encrypted with, at least
|
|
113
|
+
* 32 characters each. The first one signs and encrypts; every one is accepted, so a new secret can be put first and
|
|
114
|
+
* the old one removed once the sessions it protects have expired.
|
|
115
|
+
*/
|
|
116
|
+
cookieSecret: string | readonly string[];
|
|
117
|
+
/** Where sessions live. Defaults to an in-memory store; give a shared one (Redis…) for several instances. */
|
|
118
|
+
store?: AuthioBffSessionStore;
|
|
119
|
+
/** The route prefix of the BFF endpoints. Defaults to `/bff`. */
|
|
120
|
+
endpointPrefix?: string;
|
|
121
|
+
/** The scopes asked for at sign-in. Defaults to `openid profile email offline_access`; `openid` is always sent. */
|
|
122
|
+
scopes?: readonly string[];
|
|
123
|
+
/** Adds the `roles` scope, so the session carries the `*_resource_access` claims flattened on `/bff/user`. */
|
|
124
|
+
requestRoles?: boolean;
|
|
125
|
+
/** Calls Authio's UserInfo endpoint after sign-in and adds its claims to the session. Defaults to `false`. */
|
|
126
|
+
getClaimsFromUserInfoEndpoint?: boolean;
|
|
127
|
+
/** The `organization` sent on every sign-in unless the login request names one. */
|
|
128
|
+
organization?: string;
|
|
129
|
+
/**
|
|
130
|
+
* The `resource` (RFC 8707) sent on every sign-in unless the login request names one: the session's access token is
|
|
131
|
+
* then addressed to that API. Keep `offline_access`, so the session check introspects the refresh token.
|
|
132
|
+
*/
|
|
133
|
+
resource?: string;
|
|
134
|
+
/** The resources a login request may ask for with `?resource=`; any other is refused with `400`. */
|
|
135
|
+
allowedResources?: readonly string[];
|
|
136
|
+
/** The `prompt` sent on every sign-in unless the login request names one. */
|
|
137
|
+
prompt?: AuthioPrompt;
|
|
138
|
+
/** The `ui_locales` sent on every sign-in and sign-out unless the request names one. */
|
|
139
|
+
uiLocales?: string;
|
|
140
|
+
/** The `max_age` (seconds) sent on every sign-in unless the login request names one. */
|
|
141
|
+
maxAge?: number;
|
|
142
|
+
/** Extra authorize parameters sent on every sign-in. */
|
|
143
|
+
additionalAuthorizationParameters?: Readonly<Record<string, string>>;
|
|
144
|
+
/** Refuses an authorization response without `iss` (RFC 9207). Defaults to `true`; Authio always sends it. */
|
|
145
|
+
requireIssuerParameter?: boolean;
|
|
146
|
+
/** The redirect path registered at Authio for sign-in. Defaults to `/signin-oidc`. */
|
|
147
|
+
callbackPath?: string;
|
|
148
|
+
/** The post-logout redirect path registered at Authio. Defaults to `/signout-callback-oidc`. */
|
|
149
|
+
signedOutCallbackPath?: string;
|
|
150
|
+
/** Where the browser lands after login or logout without a `returnUrl`. Must be local. Defaults to `/`. */
|
|
151
|
+
defaultReturnUrl?: string;
|
|
152
|
+
/** The session cookie name. Defaults to `__Host-authio`; it must start with `__Host-` or `__Secure-`. */
|
|
153
|
+
cookieName?: string;
|
|
154
|
+
/** The session cookie's `SameSite`: `lax` (default) or `strict`. */
|
|
155
|
+
cookieSameSite?: AuthioBffCookieSameSite;
|
|
156
|
+
/** Seconds a session lasts without activity. Defaults to 28800 (8 hours). */
|
|
157
|
+
sessionLifetime?: number;
|
|
158
|
+
/** Renews `sessionLifetime` on activity once half of it has passed. Defaults to `true`. */
|
|
159
|
+
slidingExpiration?: boolean;
|
|
160
|
+
/** How the Authio session is checked. Defaults to `introspection`. */
|
|
161
|
+
sessionValidation?: AuthioBffSessionValidation;
|
|
162
|
+
/** Seconds between two checks of the Authio session. Defaults to 300. */
|
|
163
|
+
sessionValidationInterval?: number;
|
|
164
|
+
/** Revokes the refresh token at Authio on logout. Defaults to `true`. */
|
|
165
|
+
revokeRefreshTokenOnLogout?: boolean;
|
|
166
|
+
/** The anti-forgery header API calls must carry. Defaults to `X-CSRF`. */
|
|
167
|
+
antiForgeryHeaderName?: string;
|
|
168
|
+
/** The value the anti-forgery header must carry. Defaults to `1`. */
|
|
169
|
+
antiForgeryHeaderValue?: string;
|
|
170
|
+
/** The prefix of the keys sessions are stored under. Defaults to `authio-bff:`. */
|
|
171
|
+
sessionKeyPrefix?: string;
|
|
172
|
+
/**
|
|
173
|
+
* Serves `POST {endpointPrefix}/backchannel-logout`, which ends the sessions of the Authio session (`sid`) or person
|
|
174
|
+
* (`sub`) a logout token names. Defaults to `true`; register that URL as the client's back-channel logout URI.
|
|
175
|
+
*/
|
|
176
|
+
backChannelLogout?: boolean;
|
|
177
|
+
/** Called with errors the BFF recovers from: a failed revocation, introspection or refresh, a refused logout token. */
|
|
178
|
+
onError?: (error: unknown) => void;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Whether a URL is local to the application: it starts with a single `/` (not `//` nor `/\`) and holds no control
|
|
182
|
+
* characters, so redirecting to it cannot leave the site.
|
|
183
|
+
*/
|
|
184
|
+
declare function isLocalUrl(url: string | undefined | null): url is string;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Which token a remote API call is forwarded with, as `AuthioBffRemoteApiTokenMode` in .NET:
|
|
188
|
+
*
|
|
189
|
+
* - `user`: the person's access token (refreshed when needed); anonymous calls are refused with `401`.
|
|
190
|
+
* - `userOrNone`: the person's token when signed in, otherwise no token.
|
|
191
|
+
* - `clientCredentials`: a client-credentials token of the BFF itself, whoever is signed in.
|
|
192
|
+
* - `userOrClientCredentials`: the person's token when signed in, otherwise a client-credentials token.
|
|
193
|
+
* - `none`: no token (cookies and the anti-forgery header are still stripped).
|
|
194
|
+
*/
|
|
195
|
+
type AuthioBffRemoteApiTokenMode = "user" | "userOrNone" | "clientCredentials" | "userOrClientCredentials" | "none";
|
|
196
|
+
/**
|
|
197
|
+
* Settings of one remote API.
|
|
198
|
+
*/
|
|
199
|
+
interface AuthioBffRemoteApiOptions {
|
|
200
|
+
/** Which token the calls are forwarded with. Defaults to `user`. */
|
|
201
|
+
tokenMode?: AuthioBffRemoteApiTokenMode;
|
|
202
|
+
/**
|
|
203
|
+
* The resource (RFC 8707) the token must be issued for: a user token is exchanged for it (cached per session and
|
|
204
|
+
* resource), a client-credentials token is asked for it.
|
|
205
|
+
*/
|
|
206
|
+
resource?: string;
|
|
207
|
+
/** The scopes the token must carry; for a user token, setting them triggers a token exchange (down-scoping). */
|
|
208
|
+
scopes?: readonly string[];
|
|
209
|
+
/** Requires the anti-forgery header on every call. Defaults to `true`; turn it off only for an anonymous API. */
|
|
210
|
+
requireAntiForgery?: boolean;
|
|
211
|
+
/** When the remote API answers `401` to a user call, checks the Authio session and ends the BFF session if it is gone. Defaults to `true`. */
|
|
212
|
+
validateSessionOnUnauthorized?: boolean;
|
|
213
|
+
/** The `fetch` the calls go through. Defaults to the global one. */
|
|
214
|
+
fetch?: typeof fetch;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* The signed-in session as the application sees it: who, which Authio session, until when. Never the tokens.
|
|
219
|
+
*/
|
|
220
|
+
interface AuthioBffSessionInfo {
|
|
221
|
+
/** The person (`sub`). */
|
|
222
|
+
readonly subject: string | undefined;
|
|
223
|
+
/** The Authio session (`sid`). */
|
|
224
|
+
readonly sessionId: string | undefined;
|
|
225
|
+
/** The session's claims: the ID token's, the access token's `*_resource_access` and organization, UserInfo's. */
|
|
226
|
+
readonly claims: Readonly<Record<string, unknown>>;
|
|
227
|
+
/** When the BFF session expires, in milliseconds since the epoch. */
|
|
228
|
+
readonly expiresAt: number;
|
|
229
|
+
}
|
|
230
|
+
declare global {
|
|
231
|
+
namespace Express {
|
|
232
|
+
interface Request {
|
|
233
|
+
/** The Authio BFF session, once the BFF read it for this request; `undefined` when nobody is signed in. */
|
|
234
|
+
authioBff?: AuthioBffSessionInfo;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Options of `bff.api()`.
|
|
240
|
+
*/
|
|
241
|
+
interface AuthioBffApiOptions {
|
|
242
|
+
/** Requires the anti-forgery header. Defaults to `true`. */
|
|
243
|
+
requireAntiForgery?: boolean;
|
|
244
|
+
/** Lets anonymous calls through (with `req.authioBff` undefined) instead of answering `401`. Defaults to `false`. */
|
|
245
|
+
optional?: boolean;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* What a user token is asked for: a resource (RFC 8707) and/or narrower scopes make the BFF exchange the session's
|
|
249
|
+
* token (RFC 8693) and cache the result per session, resource and scopes.
|
|
250
|
+
*/
|
|
251
|
+
interface AuthioBffUserTokenOptions {
|
|
252
|
+
/** The resource the token must be issued for. */
|
|
253
|
+
resource?: string;
|
|
254
|
+
/** The scopes the token must carry. */
|
|
255
|
+
scopes?: readonly string[];
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* The Authio BFF: an Express middleware serving the BFF endpoints, plus the helpers the application's own routes use.
|
|
259
|
+
*/
|
|
260
|
+
interface AuthioBff extends RequestHandler {
|
|
261
|
+
/** The Authio client the BFF talks to Authio with. */
|
|
262
|
+
readonly client: AuthioClient;
|
|
263
|
+
/** Where the sessions live. */
|
|
264
|
+
readonly store: AuthioBffSessionStore;
|
|
265
|
+
/**
|
|
266
|
+
* Marks a local API: requires the anti-forgery header (`401 antiforgery_required`) and a session (`401
|
|
267
|
+
* unauthorized`), never redirects, and sets `req.authioBff`.
|
|
268
|
+
*/
|
|
269
|
+
api(options?: AuthioBffApiOptions): RequestHandler;
|
|
270
|
+
/**
|
|
271
|
+
* Forwards the calls mounted under a path to a remote API with the token `tokenMode` asks for:
|
|
272
|
+
* `app.use("/api/orders", bff.remoteApi("https://orders.example.com"))`. Mount it before any body parser.
|
|
273
|
+
*/
|
|
274
|
+
remoteApi(destination: string, options?: AuthioBffRemoteApiOptions): RequestHandler;
|
|
275
|
+
/** Reads the request's session (once per request), checking and renewing it as configured. */
|
|
276
|
+
getSession(request: Request, response: Response): Promise<AuthioBffSessionInfo | undefined>;
|
|
277
|
+
/**
|
|
278
|
+
* The person's access token, refreshed `tokenRefreshSkew` before it expires (one refresh per session at a time), or
|
|
279
|
+
* exchanged for `resource`/`scopes`. `undefined` when nobody is signed in or the session ended (a refused refresh
|
|
280
|
+
* ends it).
|
|
281
|
+
*/
|
|
282
|
+
getUserAccessToken(request: Request, response: Response, options?: AuthioBffUserTokenOptions): Promise<string | undefined>;
|
|
283
|
+
/** A `fetch` that sends the person's access token (see `getUserAccessToken`), for calling APIs from a route. */
|
|
284
|
+
userFetch(request: Request, response: Response, options?: AuthioBffUserTokenOptions & {
|
|
285
|
+
fetch?: typeof fetch;
|
|
286
|
+
}): typeof fetch;
|
|
287
|
+
/** Ends the request's session locally: deletes it from the store and clears the cookie. Authio is not told. */
|
|
288
|
+
endSession(request: Request, response: Response): Promise<void>;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Creates the Authio BFF. Mount it with `app.use(bff)`: it serves `{endpointPrefix}/login`, `/logout`, `/user`,
|
|
292
|
+
* `/silent-login`, `/silent-login-callback`, `/backchannel-logout`, the sign-in callback (`callbackPath`) and the
|
|
293
|
+
* sign-out callback (`signedOutCallbackPath`), and reads the session of every other request that carries the cookie.
|
|
294
|
+
*
|
|
295
|
+
* @throws AuthioConfigurationError naming every invalid option.
|
|
296
|
+
*/
|
|
297
|
+
declare function authioBff(options: AuthioBffOptions): AuthioBff;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* The claim types `/bff/user` adds to the person's claims.
|
|
301
|
+
*/
|
|
302
|
+
declare const AuthioBffClaimTypes: {
|
|
303
|
+
/** Seconds until the BFF session expires. */
|
|
304
|
+
readonly SessionExpiresIn: "bff:session_expires_in";
|
|
305
|
+
/** The URL the SPA navigates to in order to log out; it carries the session's `sid`. */
|
|
306
|
+
readonly LogoutUrl: "bff:logout_url";
|
|
307
|
+
/** One Authio role the person holds (flattened from the `*_resource_access` claims). */
|
|
308
|
+
readonly Role: "role";
|
|
309
|
+
/** One permission the person holds, as `{resource}:{method}` (flattened from the `*_resource_access` claims). */
|
|
310
|
+
readonly Permission: "permission";
|
|
311
|
+
};
|
|
312
|
+
/**
|
|
313
|
+
* One entry of the `/bff/user` answer: a string value, or a number for `bff:session_expires_in`.
|
|
314
|
+
*/
|
|
315
|
+
interface AuthioBffClaim {
|
|
316
|
+
readonly type: string;
|
|
317
|
+
readonly value: string | number;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* The JSON body of a BFF refusal, shaped like an OAuth error.
|
|
321
|
+
*/
|
|
322
|
+
interface AuthioBffError {
|
|
323
|
+
readonly error: string;
|
|
324
|
+
readonly error_description?: string;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export { type AuthioBff, type AuthioBffApiOptions, type AuthioBffClaim, AuthioBffClaimTypes, type AuthioBffCookieSameSite, AuthioBffDefaults, type AuthioBffError, type AuthioBffMemoryStore, type AuthioBffMemoryStoreOptions, type AuthioBffOptions, type AuthioBffRemoteApiOptions, type AuthioBffRemoteApiTokenMode, type AuthioBffSessionInfo, type AuthioBffSessionStore, type AuthioBffSessionValidation, type AuthioBffStoredSession, type AuthioBffUserTokenOptions, authioBff, createAuthioBffMemoryStore, isLocalUrl };
|