Skip to content

@btravstack/http-server


@btravstack/http-server / oidc

oidc ​

Classes ​

OidcUnreachable ​

Defined in: packages/http-server/src/oidc-discovery.ts:16

Discovery did not answer, at boot. cause is whatever openid-client rejected with — a connection refused, a document that is not one, a 404 on .well-known — kept whole rather than flattened to a message, since the three read very differently to whoever is fixing the deployment.

Extends ​

  • TaggedErrorInstance<"OidcUnreachable", { cause: unknown; issuer: string; }>

Constructors ​

Constructor ​
ts
new OidcUnreachable(args): OidcUnreachable;

Defined in: node_modules/.pnpm/unthrown@5.12.1/node_modules/unthrown/dist/index.d.mts:2386

Parameters ​
ParameterType
argsobject & object
Returns ​

OidcUnreachable

Inherited from ​

TaggedError("OidcUnreachable")<{ readonly issuer: string; readonly cause: unknown; }>.constructor

Properties ​

PropertyModifierTypeInherited fromDefined in
_tagreadonly"OidcUnreachable"TaggedError("OidcUnreachable")._tagnode_modules/.pnpm/unthrown@5.12.1/node_modules/unthrown/dist/index.d.mts:2363
causepublicunknownAuthenticatorUnavailable.causenode_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.error.d.ts:24
issuerreadonlystringTaggedError("OidcUnreachable").issuerpackages/http-server/src/oidc-discovery.ts:17
messagepublicstringTaggedError("OidcUnreachable").messagenode_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1075
namepublicstringTaggedError("OidcUnreachable").namenode_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1074
stack?publicstringTaggedError("OidcUnreachable").stacknode_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1076

Type Aliases ​

OidcAnswerer ​

ts
type OidcAnswerer<Pre, O, Needs> = Provider<HttpHandler, ConfigInvalid | OidcUnreachable, 
  | EnvReading<
  | Unpinned<O, "issuer", `${Pre}_ISSUER`>
  | Unpinned<O, "clientId", `${Pre}_CLIENT_ID`>
  | Unpinned<O, "clientSecret", `${Pre}_CLIENT_SECRET`>
  | Unpinned<O, "redirectUri", `${Pre}_REDIRECT_URI`>, 
  | MaybePinned<O, "issuer", `${Pre}_ISSUER`>
  | MaybePinned<O, "clientId", `${Pre}_CLIENT_ID`>
  | MaybePinned<O, "clientSecret", `${Pre}_CLIENT_SECRET`>
  | MaybePinned<O, "redirectUri", `${Pre}_REDIRECT_URI`>
  | Unpinned<O, "postLogoutRedirectUri", `${Pre}_POST_LOGOUT_REDIRECT_URI`>
  | MaybePinned<O, "postLogoutRedirectUri", `${Pre}_POST_LOGOUT_REDIRECT_URI`>>
  | SessionCodec
  | Observers
  | Needs
  | TokenNeeds<O>> & object;

Defined in: packages/http-server/src/oidc.ts:61

The answerer half of what oidc composes — the three login routes as one HttpHandler member. The second half is a CookieSchemes member, which carries no type of its own.

Type Parameters ​

Type ParameterDefault type
Pre extends stringstring
OOidcPins
Needsnever

Type Declaration ​

NameTypeDefined in
porttypeof HttpHandlerpackages/http-server/src/oidc.ts:80

OidcOptions ​

ts
type OidcOptions<P, Pre, D> = object;

Defined in: packages/http-server/src/oidc.ts:99

Type Parameters ​

Type ParameterDefault type
P-
Pre extends stringstring
D extends Readonly<Record<string, AnyPort>>Record<never, never>

Properties ​

PropertyModifierTypeDescriptionDefined in
allowInsecureIssuer?readonlybooleanTalk to an http: issuer that is NOT on a loopback host. Default false, and a ConfigInvalid at boot without it: the client secret, the code and every token cross the wire in cleartext, and allowInsecureRequests turns off the check that would have said so. An OPTION rather than a variable, on rule 6's own test: its silent change is a security regression, which is the argument securityHeaders is an option for. A loopback issuer — localhost, 127.0.0.1, [::1] — needs nothing, because plaintext that never leaves the machine is the dev loop's own Ory.packages/http-server/src/oidc.ts:165
clientId?readonlystringPins <prefix>_CLIENT_ID.packages/http-server/src/oidc.ts:107
clientSecret?readonlystringPins <prefix>_CLIENT_SECRET — this is a confidential client.packages/http-server/src/oidc.ts:109
inject?readonlyDThe services principal reads, as a provider's inject: each port joins the answerer's needs, and is resolved once, as the graph is built.packages/http-server/src/oidc.ts:138
issuer?readonlystringPins <prefix>_ISSUER — the provider, as its discovery document names itself.packages/http-server/src/oidc.ts:105
postLogout?readonly`/${string}`Where a logout lands when the provider advertises no end_session_endpoint. Default /.packages/http-server/src/oidc.ts:152
postLogoutRedirectUri?readonlystringPins <prefix>_POST_LOGOUT_REDIRECT_URI, an absolute HTTP(S) URI registered with the provider. Requires tokens; its server-only ID token is retrieved while revoking credentials. Without a retained hint, logout stays parameterless.packages/http-server/src/oidc.ts:121
prefix?readonly`/${string}`Where the three routes are mounted. Default /auth.packages/http-server/src/oidc.ts:131
principalreadonly(claims, services) => P | undefinedWhat the ID token's claims make the caller, given the services inject names — the same shape jwtAuthenticator takes, so one function serves both. Answering undefined refuses the login: the claim this application requires and the standard does not, such as a tenant.packages/http-server/src/oidc.ts:145
redirectUri?readonlystringPins <prefix>_REDIRECT_URI — the URI REGISTERED with the provider, which is also what the code grant is checked against. It is never rebuilt from the request's Host.packages/http-server/src/oidc.ts:115
scope?readonlystringWhat the authorization request asks for. Default openid.packages/http-server/src/oidc.ts:133
tokens?readonlyPortClassOf<string, OidcTokensService>Server-side token manager. Its binding must match this login.packages/http-server/src/oidc.ts:147
variablePrefix?readonlyPreThe prefix of the variables this login reads: <prefix>_ISSUER, <prefix>_CLIENT_ID, <prefix>_CLIENT_SECRET and <prefix>_REDIRECT_URI. The optional return URI uses <prefix>_POST_LOGOUT_REDIRECT_URI. Default HTTP_OIDC. A second login names its own — HTTP_OIDC_STAFF — the way a second jwtAuthenticator does.packages/http-server/src/oidc.ts:129

OidcPins ​

ts
type OidcPins = object;

Defined in: packages/http-server/src/oidc.ts:47

What oidc()'s pins are, as their own type: every one may be set.

Properties ​

PropertyModifierTypeDefined in
clientId?readonlystringpackages/http-server/src/oidc.ts:49
clientSecret?readonlystringpackages/http-server/src/oidc.ts:50
issuer?readonlystringpackages/http-server/src/oidc.ts:48
postLogoutRedirectUri?readonlystringpackages/http-server/src/oidc.ts:52
redirectUri?readonlystringpackages/http-server/src/oidc.ts:51

Functions ​

oidc() ​

ts
function oidc<P>(): <D, O>(options) => readonly [OidcAnswerer<PrefixOf<O, "HTTP_OIDC">, O, InstanceType<D[keyof D]>>, AnyProvider];

Defined in: packages/http-server/src/oidc.ts:487

The login answerer: three routes that log a browser in over the authorization-code flow with PKCE and hand the result to SessionCodec as a session cookie.

ts
export const BrowserApi = HttpModule("BrowserApi")({
  fragments,
  fragmentsLogin: "/auth/login",
  provides: [sessionCodec(), oidc()({ principal: identityOf })],
});
  • GET <prefix>/login?return=<path>&as=<hint> seals a PKCE verifier, state, nonce and where to return to into the five-minute __Host-oidc cookie and redirects to the provider. return is the seam htmx({ login }) writes; as rides through as login_hint.
  • GET <prefix>/callback checks state against that cookie, exchanges the code, and seals principal(claims) into __Host-session — clearing the transient in the same answer.
  • POST <prefix>/logout clears the session and sends the browser to the provider's end_session_endpoint, or to postLogout when it advertises none. A configured postLogoutRedirectUri adds the registered return and the bound server-side ID-token hint while invalidating credentials atomically.

Each of the three routes is an OPERATION reported to Observers, the way a cache read is: a refusal settles error carrying its own reason, so a rotated client secret is one dimension rather than an indistinguishable spike of bad logins. It costs a root nothing — httpServer already contributes the no-op member — and a root composing observability() gets the line for free.

It injects SessionCodec rather than holding keys, so the codec that seals a session here is the one sessionAuthenticator reads it back with — and a root composing this without sessionCodec() is di's own unmet need naming the port.

It answers TWO providers, and the second is why they are spread: the answerer, and a CookieSchemes member, because this is a cookie-reading surface and csrf's default is a fact about the graph rather than a line somebody remembered to write. Without it a root composing oidc() and sessionCodec() but no sessionAuthenticator served a state-changing POST <prefix>/logout over a cookie with CSRF off — the composition the package's own spec called "does not work at all", which nonetheless logs a browser in and exposes the logout. defineHttp already spreads the same member beside a scheme that reads one; this is that rule reaching the one surface it could not see.

Type Parameters ​

Type ParameterDefault type
Punknown

Returns ​

<D, O>(options) => readonly [OidcAnswerer<PrefixOf<O, "HTTP_OIDC">, O, InstanceType<D[keyof D]>>, AnyProvider]

Released under the MIT License.