@udibo/oauth2 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 +20 -0
- package/README.md +174 -0
- package/SECURITY.md +77 -0
- package/esm/_dnt.polyfills.d.ts +17 -0
- package/esm/_dnt.polyfills.js +15 -0
- package/esm/adapters/hono/_common.d.ts +66 -0
- package/esm/adapters/hono/_common.js +108 -0
- package/esm/adapters/hono/authorization-server.d.ts +211 -0
- package/esm/adapters/hono/authorization-server.js +214 -0
- package/esm/adapters/hono/bff/auth-request-store.d.ts +164 -0
- package/esm/adapters/hono/bff/auth-request-store.js +154 -0
- package/esm/adapters/hono/bff/bff.d.ts +852 -0
- package/esm/adapters/hono/bff/bff.js +1194 -0
- package/esm/adapters/hono/bff/cookie-prefix.d.ts +72 -0
- package/esm/adapters/hono/bff/cookie-prefix.js +85 -0
- package/esm/adapters/hono/bff/mod.d.ts +19 -0
- package/esm/adapters/hono/bff/mod.js +19 -0
- package/esm/adapters/hono/bff/proxy.d.ts +108 -0
- package/esm/adapters/hono/bff/proxy.js +314 -0
- package/esm/adapters/hono/bff/session-store.d.ts +250 -0
- package/esm/adapters/hono/bff/session-store.js +216 -0
- package/esm/adapters/hono/bff/testing.d.ts +165 -0
- package/esm/adapters/hono/bff/testing.js +254 -0
- package/esm/adapters/hono/identity.d.ts +117 -0
- package/esm/adapters/hono/identity.js +215 -0
- package/esm/adapters/hono/log.d.ts +49 -0
- package/esm/adapters/hono/log.js +66 -0
- package/esm/adapters/hono/resource-server.d.ts +101 -0
- package/esm/adapters/hono/resource-server.js +108 -0
- package/esm/client/_http.d.ts +91 -0
- package/esm/client/_http.js +303 -0
- package/esm/client/base.d.ts +184 -0
- package/esm/client/base.js +93 -0
- package/esm/client/bff-client.d.ts +235 -0
- package/esm/client/bff-client.js +280 -0
- package/esm/client/browser-storage.d.ts +152 -0
- package/esm/client/browser-storage.js +246 -0
- package/esm/client/check.d.ts +114 -0
- package/esm/client/check.js +97 -0
- package/esm/client/direct-client.d.ts +707 -0
- package/esm/client/direct-client.js +1237 -0
- package/esm/client/discovery-cache.d.ts +138 -0
- package/esm/client/discovery-cache.js +124 -0
- package/esm/client/events.d.ts +54 -0
- package/esm/client/events.js +37 -0
- package/esm/client/mod.d.ts +35 -0
- package/esm/client/mod.js +33 -0
- package/esm/client/storage.d.ts +127 -0
- package/esm/client/storage.js +81 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.d.ts +29 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.js +47 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.d.ts +28 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.js +65 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/assert.d.ts +16 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/assert.js +23 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.d.ts +25 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.js +30 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/equal.d.ts +24 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/equal.js +193 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/equals.d.ts +36 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/equals.js +61 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/exists.d.ts +17 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/exists.js +27 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/fail.d.ts +14 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/fail.js +20 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/false.d.ts +18 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/false.js +23 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater.d.ts +19 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater.js +29 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.d.ts +19 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.js +29 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.d.ts +22 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.js +52 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.d.ts +24 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.js +53 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/less.d.ts +18 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/less.js +28 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.d.ts +19 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.js +29 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/match.d.ts +17 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/match.js +26 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/mod.d.ts +43 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/mod.js +45 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.d.ts +26 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.js +39 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.d.ts +19 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.js +29 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.d.ts +17 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.js +26 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.d.ts +22 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.js +32 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.d.ts +28 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.js +177 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.d.ts +41 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.js +53 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.d.ts +23 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.js +58 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.d.ts +17 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.js +26 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/throws.d.ts +44 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/throws.js +44 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.d.ts +14 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.js +20 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.d.ts +14 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.js +20 -0
- package/esm/deps/jsr.io/@std/async/1.5.0/delay.d.ts +44 -0
- package/esm/deps/jsr.io/@std/async/1.5.0/delay.js +84 -0
- package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.d.ts +39 -0
- package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.js +61 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.d.ts +22 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.js +51 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.d.ts +34 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.js +192 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.d.ts +34 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.js +113 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.d.ts +3 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.js +13 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.d.ts +8 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.js +2 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.d.ts +1 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.js +26 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.d.ts +60 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.js +152 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.d.ts +39 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.js +87 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.d.ts +39 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.js +131 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.d.ts +39 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.js +82 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.d.ts +39 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.js +72 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.d.ts +38 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.js +87 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.d.ts +97 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.js +99 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.d.ts +119 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.js +209 -0
- package/esm/deps/jsr.io/@std/http/1.1.3/status.d.ts +343 -0
- package/esm/deps/jsr.io/@std/http/1.1.3/status.js +343 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.d.ts +159 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.js +223 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.d.ts +79 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.js +116 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff.d.ts +139 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff.js +277 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.d.ts +98 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.js +181 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/format.d.ts +29 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/format.js +68 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/styles.d.ts +158 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/styles.js +206 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/types.d.ts +24 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/types.js +3 -0
- package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.d.ts +91 -0
- package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.js +347 -0
- package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.d.ts +382 -0
- package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.js +1110 -0
- package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.d.ts +363 -0
- package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.js +616 -0
- package/esm/errors.d.ts +203 -0
- package/esm/errors.js +287 -0
- package/esm/identity/captcha.d.ts +147 -0
- package/esm/identity/captcha.js +64 -0
- package/esm/identity/delivery.d.ts +96 -0
- package/esm/identity/delivery.js +41 -0
- package/esm/identity/errors.d.ts +39 -0
- package/esm/identity/errors.js +46 -0
- package/esm/identity/events.d.ts +242 -0
- package/esm/identity/events.js +39 -0
- package/esm/identity/external/_shared.d.ts +86 -0
- package/esm/identity/external/_shared.js +107 -0
- package/esm/identity/external/_token-exchange.d.ts +61 -0
- package/esm/identity/external/_token-exchange.js +179 -0
- package/esm/identity/external/apple-client-secret.d.ts +82 -0
- package/esm/identity/external/apple-client-secret.js +133 -0
- package/esm/identity/external/apple.d.ts +64 -0
- package/esm/identity/external/apple.js +248 -0
- package/esm/identity/external/discord.d.ts +38 -0
- package/esm/identity/external/discord.js +62 -0
- package/esm/identity/external/errors.d.ts +48 -0
- package/esm/identity/external/errors.js +36 -0
- package/esm/identity/external/flow.d.ts +107 -0
- package/esm/identity/external/flow.js +158 -0
- package/esm/identity/external/github.d.ts +42 -0
- package/esm/identity/external/github.js +164 -0
- package/esm/identity/external/google.d.ts +52 -0
- package/esm/identity/external/google.js +38 -0
- package/esm/identity/external/mod.d.ts +63 -0
- package/esm/identity/external/mod.js +61 -0
- package/esm/identity/external/oauth2.d.ts +110 -0
- package/esm/identity/external/oauth2.js +158 -0
- package/esm/identity/external/oidc.d.ts +81 -0
- package/esm/identity/external/oidc.js +250 -0
- package/esm/identity/external/provider.d.ts +132 -0
- package/esm/identity/external/provider.js +12 -0
- package/esm/identity/hibp.d.ts +67 -0
- package/esm/identity/hibp.js +87 -0
- package/esm/identity/identifier.d.ts +44 -0
- package/esm/identity/identifier.js +47 -0
- package/esm/identity/lockout.d.ts +155 -0
- package/esm/identity/lockout.js +117 -0
- package/esm/identity/mfa/mod.d.ts +42 -0
- package/esm/identity/mfa/mod.js +42 -0
- package/esm/identity/mfa/recovery-codes.d.ts +96 -0
- package/esm/identity/mfa/recovery-codes.js +80 -0
- package/esm/identity/mfa/service.d.ts +338 -0
- package/esm/identity/mfa/service.js +351 -0
- package/esm/identity/mfa/totp.d.ts +186 -0
- package/esm/identity/mfa/totp.js +194 -0
- package/esm/identity/migration.d.ts +155 -0
- package/esm/identity/migration.js +243 -0
- package/esm/identity/mod.d.ts +65 -0
- package/esm/identity/mod.js +64 -0
- package/esm/identity/otp.d.ts +255 -0
- package/esm/identity/otp.js +245 -0
- package/esm/identity/password-policy.d.ts +45 -0
- package/esm/identity/password-policy.js +60 -0
- package/esm/identity/password.d.ts +171 -0
- package/esm/identity/password.js +160 -0
- package/esm/identity/rate-limit.d.ts +192 -0
- package/esm/identity/rate-limit.js +168 -0
- package/esm/identity/service.d.ts +579 -0
- package/esm/identity/service.js +835 -0
- package/esm/identity/session.d.ts +122 -0
- package/esm/identity/session.js +48 -0
- package/esm/identity/token-flow.d.ts +214 -0
- package/esm/identity/token-flow.js +173 -0
- package/esm/models/authentication.d.ts +24 -0
- package/esm/models/authentication.js +1 -0
- package/esm/models/authorization-code.d.ts +63 -0
- package/esm/models/authorization-code.js +1 -0
- package/esm/models/authorization.d.ts +165 -0
- package/esm/models/authorization.js +160 -0
- package/esm/models/client.d.ts +78 -0
- package/esm/models/client.js +1 -0
- package/esm/models/device-authorization.d.ts +30 -0
- package/esm/models/device-authorization.js +1 -0
- package/esm/models/responses.d.ts +93 -0
- package/esm/models/responses.js +1 -0
- package/esm/models/scope.d.ts +81 -0
- package/esm/models/scope.js +125 -0
- package/esm/models/token.d.ts +135 -0
- package/esm/models/token.js +1 -0
- package/esm/package.json +3 -0
- package/esm/react/callback.d.ts +52 -0
- package/esm/react/callback.js +71 -0
- package/esm/react/components/class-names.d.ts +91 -0
- package/esm/react/components/class-names.js +118 -0
- package/esm/react/components/internal.d.ts +62 -0
- package/esm/react/components/internal.js +102 -0
- package/esm/react/components/mfa-challenge-form.d.ts +66 -0
- package/esm/react/components/mfa-challenge-form.js +46 -0
- package/esm/react/components/mfa-enrollment-form.d.ts +83 -0
- package/esm/react/components/mfa-enrollment-form.js +41 -0
- package/esm/react/components/mod.d.ts +103 -0
- package/esm/react/components/mod.js +93 -0
- package/esm/react/components/request-password-reset-form.d.ts +61 -0
- package/esm/react/components/request-password-reset-form.js +41 -0
- package/esm/react/components/reset-password-form.d.ts +66 -0
- package/esm/react/components/reset-password-form.js +56 -0
- package/esm/react/components/sign-in-form.d.ts +74 -0
- package/esm/react/components/sign-in-form.js +43 -0
- package/esm/react/components/sign-up-form.d.ts +85 -0
- package/esm/react/components/sign-up-form.js +57 -0
- package/esm/react/components/types.d.ts +208 -0
- package/esm/react/components/types.js +14 -0
- package/esm/react/components/use-auth-form.d.ts +118 -0
- package/esm/react/components/use-auth-form.js +172 -0
- package/esm/react/components/user-menu.d.ts +79 -0
- package/esm/react/components/user-menu.js +73 -0
- package/esm/react/context.d.ts +51 -0
- package/esm/react/context.js +16 -0
- package/esm/react/mod.d.ts +26 -0
- package/esm/react/mod.js +26 -0
- package/esm/react/provider.d.ts +40 -0
- package/esm/react/provider.js +189 -0
- package/esm/react/require-auth.d.ts +31 -0
- package/esm/react/require-auth.js +37 -0
- package/esm/react/testing.d.ts +322 -0
- package/esm/react/testing.js +461 -0
- package/esm/react/use-authorization.d.ts +31 -0
- package/esm/react/use-authorization.js +36 -0
- package/esm/react/use-oauth2.d.ts +112 -0
- package/esm/react/use-oauth2.js +127 -0
- package/esm/server/authorization-server.d.ts +736 -0
- package/esm/server/authorization-server.js +1304 -0
- package/esm/server/client-authentication.d.ts +28 -0
- package/esm/server/client-authentication.js +51 -0
- package/esm/server/grants/authorization-code.d.ts +179 -0
- package/esm/server/grants/authorization-code.js +232 -0
- package/esm/server/grants/client-credentials.d.ts +52 -0
- package/esm/server/grants/client-credentials.js +53 -0
- package/esm/server/grants/device-authorization.d.ts +103 -0
- package/esm/server/grants/device-authorization.js +156 -0
- package/esm/server/grants/grant.d.ts +165 -0
- package/esm/server/grants/grant.js +173 -0
- package/esm/server/grants/password.d.ts +80 -0
- package/esm/server/grants/password.js +92 -0
- package/esm/server/grants/refresh-token.d.ts +80 -0
- package/esm/server/grants/refresh-token.js +136 -0
- package/esm/server/introspection-token-reader.d.ts +153 -0
- package/esm/server/introspection-token-reader.js +164 -0
- package/esm/server/jwks-token-reader.d.ts +263 -0
- package/esm/server/jwks-token-reader.js +508 -0
- package/esm/server/mod.d.ts +23 -0
- package/esm/server/mod.js +15 -0
- package/esm/server/public-suffix/mod.d.ts +62 -0
- package/esm/server/public-suffix/mod.js +103 -0
- package/esm/server/public-suffix/rules.d.ts +23 -0
- package/esm/server/public-suffix/rules.js +23 -0
- package/esm/server/redirect-uri.d.ts +187 -0
- package/esm/server/redirect-uri.js +331 -0
- package/esm/server/resource-server.d.ts +286 -0
- package/esm/server/resource-server.js +365 -0
- package/esm/server/services/authorization-code.d.ts +84 -0
- package/esm/server/services/authorization-code.js +35 -0
- package/esm/server/services/client.d.ts +35 -0
- package/esm/server/services/client.js +1 -0
- package/esm/server/services/device-authorization.d.ts +134 -0
- package/esm/server/services/device-authorization.js +7 -0
- package/esm/server/services/token.d.ts +242 -0
- package/esm/server/services/token.js +78 -0
- package/esm/server/services/user.d.ts +29 -0
- package/esm/server/services/user.js +7 -0
- package/esm/server/signing-keys.d.ts +201 -0
- package/esm/server/signing-keys.js +225 -0
- package/esm/server/utils/hash.d.ts +6 -0
- package/esm/server/utils/hash.js +11 -0
- package/esm/testing/contract/authorization-code.d.ts +37 -0
- package/esm/testing/contract/authorization-code.js +93 -0
- package/esm/testing/contract/client.d.ts +85 -0
- package/esm/testing/contract/client.js +126 -0
- package/esm/testing/contract/device-authorization.d.ts +37 -0
- package/esm/testing/contract/device-authorization.js +193 -0
- package/esm/testing/contract/lockout-store.d.ts +47 -0
- package/esm/testing/contract/lockout-store.js +122 -0
- package/esm/testing/contract/mfa-store.d.ts +50 -0
- package/esm/testing/contract/mfa-store.js +226 -0
- package/esm/testing/contract/mod.d.ts +39 -0
- package/esm/testing/contract/mod.js +39 -0
- package/esm/testing/contract/otp-store.d.ts +48 -0
- package/esm/testing/contract/otp-store.js +197 -0
- package/esm/testing/contract/rate-limit-store.d.ts +52 -0
- package/esm/testing/contract/rate-limit-store.js +105 -0
- package/esm/testing/contract/token-flow-store.d.ts +54 -0
- package/esm/testing/contract/token-flow-store.js +179 -0
- package/esm/testing/contract/token-reader.d.ts +77 -0
- package/esm/testing/contract/token-reader.js +78 -0
- package/esm/testing/contract/token.d.ts +61 -0
- package/esm/testing/contract/token.js +287 -0
- package/esm/testing/contract/user.d.ts +63 -0
- package/esm/testing/contract/user.js +75 -0
- package/esm/testing/mod.d.ts +34 -0
- package/esm/testing/mod.js +34 -0
- package/esm/testing/server.d.ts +197 -0
- package/esm/testing/server.js +193 -0
- package/esm/testing/services.d.ts +373 -0
- package/esm/testing/services.js +703 -0
- package/esm/utils/_buffer.d.ts +10 -0
- package/esm/utils/_buffer.js +12 -0
- package/esm/utils/_timing-safe.d.ts +13 -0
- package/esm/utils/_timing-safe.js +22 -0
- package/esm/utils/authentication-context.d.ts +3 -0
- package/esm/utils/authentication-context.js +14 -0
- package/esm/utils/basic-auth.d.ts +50 -0
- package/esm/utils/basic-auth.js +106 -0
- package/esm/utils/crypto.d.ts +97 -0
- package/esm/utils/crypto.js +162 -0
- package/esm/utils/pkce.d.ts +151 -0
- package/esm/utils/pkce.js +179 -0
- package/esm/utils/text.d.ts +24 -0
- package/esm/utils/text.js +28 -0
- package/esm/utils/url.d.ts +66 -0
- package/esm/utils/url.js +90 -0
- package/package.json +92 -0
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend-for-Frontend (BFF) adapter for Hono.
|
|
3
|
+
*
|
|
4
|
+
* Wraps a {@link DirectClient} configured with a client secret
|
|
5
|
+
* and exposes the session endpoints a browser SPA talks to:
|
|
6
|
+
*
|
|
7
|
+
* - `POST /auth/login` — redirects the browser to the authorize endpoint,
|
|
8
|
+
* persisting a per-request state/verifier record and binding its `state` to
|
|
9
|
+
* this browser with a short-lived cookie.
|
|
10
|
+
* - `GET /auth/callback` — requires the browser to present that binding
|
|
11
|
+
* cookie, exchanges the code, opens a session (or, in
|
|
12
|
+
* `sessionMode: "shared"`, attaches the tokens to the app's existing
|
|
13
|
+
* session), and redirects to the app's `return_to` path.
|
|
14
|
+
* - `GET|POST /auth/logout` — destroys the session and (best-effort) revokes
|
|
15
|
+
* the refresh token upstream. Registered for both methods because the
|
|
16
|
+
* shipped React client navigates the browser here (a GET).
|
|
17
|
+
* - `GET /auth/session` — returns `{ isAuthenticated, user }` from the
|
|
18
|
+
* session. **Never** exposes tokens to the browser.
|
|
19
|
+
* - `POST /auth/backchannel` — OIDC Back-Channel Logout receiver, mounted only
|
|
20
|
+
* when `backchannelLogout` is configured. A server-to-server call from the OP
|
|
21
|
+
* that ends the matching server-side session(s).
|
|
22
|
+
*
|
|
23
|
+
* A companion {@link HonoBff.attachToken} middleware reads the session
|
|
24
|
+
* cookie, looks up / refreshes the access token, and sets
|
|
25
|
+
* `Authorization: Bearer <token>` on the inbound request so a co-located
|
|
26
|
+
* resource-server adapter can validate it normally — all in-process, no
|
|
27
|
+
* outbound HTTP.
|
|
28
|
+
*
|
|
29
|
+
* The credentialed surface is **CSRF-protected by default**: a request that
|
|
30
|
+
* carries the session cookie must also carry a custom `x-csrf` header (the
|
|
31
|
+
* shipped `BffClient` and React adapter send it automatically), and the
|
|
32
|
+
* `GET /auth/logout` navigation — which cannot carry one, and is what the
|
|
33
|
+
* shipped client uses — is guarded by a same-origin check that fails closed.
|
|
34
|
+
* `POST /auth/logout` is the stronger path to adopt. See
|
|
35
|
+
* {@link HonoBffCsrfOptions}; pass `csrf: false` to disable.
|
|
36
|
+
*
|
|
37
|
+
* Sign-in itself is bound to the browser that started it, and that binding is
|
|
38
|
+
* **not** configurable: `/auth/login` records the `state` it minted in a
|
|
39
|
+
* short-lived `HttpOnly` `SameSite=Lax` cookie, and `/auth/callback` refuses
|
|
40
|
+
* any authorization response whose `state` that cookie does not vouch for. A
|
|
41
|
+
* pending authorization is therefore never completable from a browser other
|
|
42
|
+
* than the one that began it, whatever
|
|
43
|
+
* {@link HonoBffOptions.authRequestStorage} it is kept in — without it an
|
|
44
|
+
* attacker could start a sign-in and hand the victim the callback URL to log
|
|
45
|
+
* the victim into the attacker's account.
|
|
46
|
+
*
|
|
47
|
+
* @module
|
|
48
|
+
*/
|
|
49
|
+
import { Hono } from "hono";
|
|
50
|
+
import type { Context, Handler, MiddlewareHandler } from "hono";
|
|
51
|
+
import type { DirectClient, TokenBundle, UserInfoClaims } from "../../../client/mod.js";
|
|
52
|
+
import type { ClientInterface } from "../../../models/client.js";
|
|
53
|
+
import type { AbstractScope, BasicScope } from "../../../models/scope.js";
|
|
54
|
+
import type { HonoAuthorizationServer } from "../authorization-server.js";
|
|
55
|
+
import type { HonoResourceServer } from "../resource-server.js";
|
|
56
|
+
import type { AuthRequestStorageFactory } from "./auth-request-store.js";
|
|
57
|
+
import { type SessionStore } from "./session-store.js";
|
|
58
|
+
import { type HonoBffProxyOptions } from "./proxy.js";
|
|
59
|
+
/**
|
|
60
|
+
* A resource server the BFF can compose with `attachToken()` to expose a
|
|
61
|
+
* single `bff.protect()` middleware.
|
|
62
|
+
*
|
|
63
|
+
* Accepted in two shapes so both deployment topologies stay symmetric:
|
|
64
|
+
*
|
|
65
|
+
* - **Own-issuer** (the Udibo case): pass a {@link HonoAuthorizationServer}.
|
|
66
|
+
* `protect()` validates against the same in-process token service the
|
|
67
|
+
* issuer uses — no introspection round-trip.
|
|
68
|
+
* - **Third-party-issuer**: pass a {@link HonoResourceServer} configured
|
|
69
|
+
* with `IntrospectionTokenReader`. `protect()` validates via RFC 7662
|
|
70
|
+
* against the upstream.
|
|
71
|
+
*/
|
|
72
|
+
export type HonoBffResourceServer<Client extends ClientInterface = any, User = any, S extends AbstractScope = BasicScope> = HonoResourceServer<Client, User, S> | HonoAuthorizationServer<Client, User, S>;
|
|
73
|
+
/**
|
|
74
|
+
* Cookie settings for the BFF session cookie. Defaults follow the IETF
|
|
75
|
+
* "OAuth 2.0 for Browser-Based Apps" BCP §6.1.3.2: `Secure`, `HttpOnly`,
|
|
76
|
+
* `SameSite=Strict`, `Path=/`, no `Domain`, and the `__Host-` name prefix.
|
|
77
|
+
*
|
|
78
|
+
* The **default** name is prefix-aware: it carries `__Host-` only when every
|
|
79
|
+
* attribute that prefix requires holds, and falls back to the bare name
|
|
80
|
+
* otherwise. An **explicit** {@link name} whose prefix contradicts the other
|
|
81
|
+
* attributes throws from the {@link HonoBff} constructor instead of emitting a
|
|
82
|
+
* cookie every browser silently drops.
|
|
83
|
+
*/
|
|
84
|
+
export interface HonoBffCookieOptions {
|
|
85
|
+
/**
|
|
86
|
+
* Cookie name. Defaults to `"__Host-oauth2_session"` (the BCP `__Host-`
|
|
87
|
+
* prefix) when the other options satisfy that prefix — {@link secure} on,
|
|
88
|
+
* {@link path} `"/"`, and no {@link domain} — and to `"oauth2_session"`
|
|
89
|
+
* otherwise, because a browser rejects a `__Host-` cookie that breaks any of
|
|
90
|
+
* them.
|
|
91
|
+
*
|
|
92
|
+
* Setting this explicitly to a `__Host-` or `__Secure-` prefixed name throws
|
|
93
|
+
* from the {@link HonoBff} constructor when the other options contradict the
|
|
94
|
+
* prefix. Prefixes are matched case-insensitively, as browsers match them.
|
|
95
|
+
*/
|
|
96
|
+
name?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Cookie path. Defaults to `"/"`. Any other value drops the default
|
|
99
|
+
* `__Host-` prefix, which requires `Path=/`.
|
|
100
|
+
*/
|
|
101
|
+
path?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Marks the cookie Secure. Defaults to `true`. Turning it off drops the
|
|
104
|
+
* default `__Host-` prefix, which requires `Secure`.
|
|
105
|
+
*/
|
|
106
|
+
secure?: boolean;
|
|
107
|
+
/** Marks the cookie HttpOnly. Defaults to `true`. */
|
|
108
|
+
httpOnly?: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* SameSite attribute. Defaults to `"Strict"` (per the BCP). Use `"Lax"` if
|
|
111
|
+
* you run `sessionMode: "shared"` with a cross-origin IDP, so the cookie
|
|
112
|
+
* survives the cross-site callback return.
|
|
113
|
+
*
|
|
114
|
+
* `"None"` requires {@linkcode HonoBffCookieOptions.secure}; the constructor
|
|
115
|
+
* throws on the combination rather than emitting a cookie every modern
|
|
116
|
+
* browser silently drops.
|
|
117
|
+
*/
|
|
118
|
+
sameSite?: "Lax" | "Strict" | "None";
|
|
119
|
+
/**
|
|
120
|
+
* Optional cookie domain. The BCP recommends omitting it (host-bound).
|
|
121
|
+
* Setting it drops the default `__Host-` prefix, which forbids `Domain`.
|
|
122
|
+
*/
|
|
123
|
+
domain?: string;
|
|
124
|
+
/**
|
|
125
|
+
* `Max-Age` in seconds, for the **cookie only**. Defaults to
|
|
126
|
+
* {@linkcode HonoBffOptions.sessionMaxAgeMs}, so the browser keeps the
|
|
127
|
+
* cookie exactly as long as the session behind it is honored.
|
|
128
|
+
*
|
|
129
|
+
* Set it to lengthen the cookie past the session (harmless: the session ends
|
|
130
|
+
* first and the stale cookie is cleared). Setting it **shorter** than the
|
|
131
|
+
* session lifetime throws from the {@link HonoBff} constructor — that is the
|
|
132
|
+
* pairing where the browser drops the cookie while the session stays active
|
|
133
|
+
* and listed.
|
|
134
|
+
*
|
|
135
|
+
* Pass `"session"` for a true session cookie (no `Max-Age`, gone when the
|
|
136
|
+
* browser closes). The server-side bound still applies, so the two agree:
|
|
137
|
+
* an ephemeral cookie shortens the window, it never unbounds the session.
|
|
138
|
+
*/
|
|
139
|
+
maxAge?: number | "session";
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* CSRF defense for the BFF's credentialed surface.
|
|
143
|
+
*
|
|
144
|
+
* The BFF's session cookie is an ambient credential the browser attaches
|
|
145
|
+
* automatically, so a cross-site page can trigger credentialed requests
|
|
146
|
+
* (classic CSRF). The defense (as used by Duende.BFF's `X-CSRF` and Curity's
|
|
147
|
+
* `token-handler-version` headers) requires a **custom request header** on the
|
|
148
|
+
* fetch-driven surface: a custom header makes the request "non-simple", forcing
|
|
149
|
+
* a CORS preflight that an untrusted origin cannot satisfy, so the browser never
|
|
150
|
+
* attaches the cookie. The shipped `BffClient` (and therefore the React
|
|
151
|
+
* adapter) sends this header automatically.
|
|
152
|
+
*
|
|
153
|
+
* Enforced only when the session cookie is actually present (an anonymous
|
|
154
|
+
* request has nothing to abuse). Applies to `GET /auth/session`, the
|
|
155
|
+
* `POST /auth/logout` path, and {@link HonoBff.protect} / {@link HonoBff.attachToken}'s
|
|
156
|
+
* cookie path.
|
|
157
|
+
*
|
|
158
|
+
* `POST /auth/logout` is the **preferred** sign-out path because it is covered
|
|
159
|
+
* by that header check. The `GET /auth/logout` navigation cannot carry a custom
|
|
160
|
+
* header, so it is guarded instead by a same-origin check that reads
|
|
161
|
+
* `Sec-Fetch-Site` (accepting only `same-origin` and `none`) and falls back to
|
|
162
|
+
* comparing `Origin`, else `Referer`, for clients that send no fetch metadata.
|
|
163
|
+
* `same-site` is rejected along with `cross-site`, so a `GET` logout link
|
|
164
|
+
* followed from a sibling subdomain does not pass. That check **fails closed**:
|
|
165
|
+
* a request carrying none of the three signals is rejected. Two residuals come
|
|
166
|
+
* with it. A `GET` logout link followed from a no-referrer page on a browser too
|
|
167
|
+
* old to send `Sec-Fetch-Site` stops working. And because
|
|
168
|
+
* `Sec-Fetch-Site: none` survives redirects by design (W3C Fetch Metadata §4.1),
|
|
169
|
+
* a navigation with no browser initiator — the address bar, a bookmark, or a
|
|
170
|
+
* link opened from a native app such as an email client — that redirects into
|
|
171
|
+
* the `GET` route still passes.
|
|
172
|
+
*
|
|
173
|
+
* **Both residuals apply to the shipped client today**, which signs out by
|
|
174
|
+
* navigating the browser to `GET /auth/logout` (`BffClient.logout()` returns
|
|
175
|
+
* the URL and the React adapter assigns it to `location`). `POST` is the path
|
|
176
|
+
* to **adopt**, not one you already get: wire your sign-out control to post to
|
|
177
|
+
* this route with the `x-csrf` header if a forgeable `GET` sign-out matters to
|
|
178
|
+
* you.
|
|
179
|
+
*
|
|
180
|
+
* Pass `csrf: false` to disable (e.g. an example whose page uses hand-written
|
|
181
|
+
* `fetch` for readability, or an app that has not yet migrated its call sites).
|
|
182
|
+
*/
|
|
183
|
+
export interface HonoBffCsrfOptions {
|
|
184
|
+
/**
|
|
185
|
+
* Header name required on credentialed fetch requests. Defaults to `"x-csrf"`.
|
|
186
|
+
* Matched case-insensitively.
|
|
187
|
+
*/
|
|
188
|
+
headerName?: string;
|
|
189
|
+
/**
|
|
190
|
+
* If set, the header must equal this value. Otherwise any non-empty value is
|
|
191
|
+
* accepted — presence is what forces the CORS preflight. Defaults to presence.
|
|
192
|
+
*/
|
|
193
|
+
headerValue?: string;
|
|
194
|
+
}
|
|
195
|
+
/** Paths for the session endpoints mounted by {@link HonoBff.routes}. */
|
|
196
|
+
export interface HonoBffPaths {
|
|
197
|
+
/**
|
|
198
|
+
* The base the caller mounts {@link HonoBff.routes} under — stripped from
|
|
199
|
+
* the paths below so each handler registers at the right sub-path. Defaults
|
|
200
|
+
* to `/auth`, matching the default paths and the documented
|
|
201
|
+
* `app.route("/auth", bff.routes())`.
|
|
202
|
+
*
|
|
203
|
+
* Set it whenever you move the surface: with
|
|
204
|
+
* `basePath: "/session", login: "/session/login"` the router registers
|
|
205
|
+
* `/login`, so `app.route("/session", bff.routes())` serves
|
|
206
|
+
* `/session/login`. Pass `""` to mount the paths verbatim at the app root.
|
|
207
|
+
*
|
|
208
|
+
* A trailing slash is ignored. Unless it is `""`, **every path that gets
|
|
209
|
+
* mounted must sit strictly under it** (`${basePath}/…`) — the constructor
|
|
210
|
+
* throws otherwise, because `routes()` strips the base to mount each handler
|
|
211
|
+
* while {@link HonoBff.loginContinuation} and the session probe's `logoutUrl`
|
|
212
|
+
* hand out the unstripped paths, so a stray path would be served at one URL
|
|
213
|
+
* and advertised at another. {@link HonoBffPaths.backchannel} is exempt
|
|
214
|
+
* unless {@link HonoBffOptions.backchannelLogout} is configured, since
|
|
215
|
+
* `routes()` mounts it only then.
|
|
216
|
+
*/
|
|
217
|
+
basePath?: string;
|
|
218
|
+
/** Where the browser is sent to begin sign-in. Defaults to `/auth/login`. */
|
|
219
|
+
login?: string;
|
|
220
|
+
/** Redirect target the IDP returns to after authorization. Defaults to `/auth/callback`. */
|
|
221
|
+
callback?: string;
|
|
222
|
+
/** Ends the session and clears the cookie. Defaults to `/auth/logout`. */
|
|
223
|
+
logout?: string;
|
|
224
|
+
/** Probed by the SPA for `{ isAuthenticated, user }`. Defaults to `/auth/session`. */
|
|
225
|
+
session?: string;
|
|
226
|
+
/** OIDC Back-Channel Logout receiver. Defaults to `/auth/backchannel`. */
|
|
227
|
+
backchannel?: string;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The subject / OP-session-id a validated OIDC `logout_token` says to log out.
|
|
231
|
+
* Returned by {@link HonoBffBackchannelOptions.verifyLogoutToken}.
|
|
232
|
+
*/
|
|
233
|
+
export interface BackchannelLogoutSubject {
|
|
234
|
+
/** The `sub` claim — log out all of the subject's sessions when no `sid`. */
|
|
235
|
+
sub?: string;
|
|
236
|
+
/** The `sid` claim — log out only the session with this OP session id. */
|
|
237
|
+
sid?: string;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* OIDC Back-Channel Logout configuration. When set, {@link HonoBff.routes}
|
|
241
|
+
* mounts a `POST /auth/backchannel` receiver.
|
|
242
|
+
*
|
|
243
|
+
* **Signature verification is yours.** The package stays dependency-free, so you
|
|
244
|
+
* supply `verifyLogoutToken`: verify the JWT's signature (against the OP's JWKS,
|
|
245
|
+
* e.g. with `jose`, or in-process for a co-located IDP) **and** its claims
|
|
246
|
+
* (`iss`, `aud`, `iat`, `exp`, the required `events` member
|
|
247
|
+
* `http://schemas.openid.net/event/backchannel-logout`, `sub`/`sid` presence,
|
|
248
|
+
* `nonce` absence, and `jti` replay), then return the `{ sub, sid }` to log out.
|
|
249
|
+
* Throw or return `null` to reject (the receiver responds `400`).
|
|
250
|
+
*
|
|
251
|
+
* Requires a {@link SessionStore} that implements `destroyByLogout`
|
|
252
|
+
* (the in-memory and DB-backed stateful stores do; the stateless encrypted
|
|
253
|
+
* cookie store cannot — construction throws if the store can't support it).
|
|
254
|
+
*/
|
|
255
|
+
export interface HonoBffBackchannelOptions {
|
|
256
|
+
/**
|
|
257
|
+
* Verifies a raw `logout_token` JWT (signature and claims) and returns the
|
|
258
|
+
* `{ sub, sid }` to log out. Throw or return `null` to reject the request.
|
|
259
|
+
*/
|
|
260
|
+
verifyLogoutToken: (logoutToken: string) => Promise<BackchannelLogoutSubject | null> | BackchannelLogoutSubject | null;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Why `/auth/callback` could not complete a sign-in. Passed to
|
|
264
|
+
* {@link HonoBffOptions.onCallbackError} so the app can render or redirect to a
|
|
265
|
+
* friendly page instead of the default JSON.
|
|
266
|
+
*/
|
|
267
|
+
export interface BffCallbackError {
|
|
268
|
+
/**
|
|
269
|
+
* OAuth2-style error code: the IDP's `error` (e.g. `access_denied`),
|
|
270
|
+
* `invalid_request` (missing `code`/`state`, or a `state` this browser never
|
|
271
|
+
* started — see {@link HonoBffOptions.loginStateTtlMs}), or `invalid_grant`
|
|
272
|
+
* (the code exchange failed — expired/replayed code, unknown `state`).
|
|
273
|
+
*/
|
|
274
|
+
error: string;
|
|
275
|
+
/** Human-readable description; never contains secrets. */
|
|
276
|
+
error_description: string;
|
|
277
|
+
/** The thrown cause for the `invalid_grant` (exchange) case; otherwise `undefined`. */
|
|
278
|
+
cause?: unknown;
|
|
279
|
+
}
|
|
280
|
+
/** Options accepted by the {@link HonoBff} constructor. */
|
|
281
|
+
export interface HonoBffOptions {
|
|
282
|
+
/**
|
|
283
|
+
* OAuth2 client for the authorization server, constructed with a
|
|
284
|
+
* `clientSecret`. The BFF drives the flows through its
|
|
285
|
+
* `exchangeAuthorizationCode` / `exchangeRefreshToken` primitives; tokens
|
|
286
|
+
* are stored in {@link HonoBffOptions.sessionStore} — **not** in the
|
|
287
|
+
* client's token storage.
|
|
288
|
+
*/
|
|
289
|
+
client: DirectClient;
|
|
290
|
+
/**
|
|
291
|
+
* Where session data lives. Defaults to {@link MemorySessionStore},
|
|
292
|
+
* which is fine for dev and tests but must be replaced for production.
|
|
293
|
+
*/
|
|
294
|
+
sessionStore?: SessionStore;
|
|
295
|
+
/**
|
|
296
|
+
* How long a BFF session is meant to live, in milliseconds. Defaults to 14
|
|
297
|
+
* days ({@link DEFAULT_SESSION_MAX_AGE_MS}). Milliseconds match the store's
|
|
298
|
+
* `maxAgeMs` and `loginStateTtlMs`; `cookie.maxAge` stays in seconds because
|
|
299
|
+
* that is the unit of the HTTP `Max-Age` attribute it sets.
|
|
300
|
+
*
|
|
301
|
+
* This is the **one** place the lifetime is stated. It sets the session
|
|
302
|
+
* cookie's `Max-Age` (see {@linkcode HonoBffCookieOptions.maxAge}) and, in
|
|
303
|
+
* the default `sessionMode: "own"`, it is enforced on every read: a session
|
|
304
|
+
* older than this is destroyed and its cookie cleared, so the cookie and the
|
|
305
|
+
* record behind it can never disagree about whether the user is signed in.
|
|
306
|
+
* Measured from `createdAt`, so it caps the session absolutely rather than
|
|
307
|
+
* rolling forward with each token refresh. A store that does not persist
|
|
308
|
+
* `createdAt` therefore opts its sessions out of this cap entirely — they
|
|
309
|
+
* never age out. `runSessionStoreContractTests` pins that round-trip, so run
|
|
310
|
+
* it against any store you write.
|
|
311
|
+
*
|
|
312
|
+
* Applies in the default `sessionMode: "own"` only. In
|
|
313
|
+
* `sessionMode: "shared"` the app owns the session record and its cookie's
|
|
314
|
+
* lifetime — often per session (remember-me vs ephemeral) — so this value is
|
|
315
|
+
* ignored there: the BFF writes only an explicit {@linkcode
|
|
316
|
+
* HonoBffCookieOptions.maxAge}, or an ephemeral cookie the app re-stamps.
|
|
317
|
+
*
|
|
318
|
+
* A store that advertises its own bound (see `BoundedSessionStore`, which
|
|
319
|
+
* {@linkcode EncryptedCookieSessionStore} implements) is cross-checked at
|
|
320
|
+
* construction: if the cookie would die before that store stops honoring the
|
|
321
|
+
* session, the constructor throws instead of shipping the mismatch.
|
|
322
|
+
*/
|
|
323
|
+
sessionMaxAgeMs?: number;
|
|
324
|
+
/**
|
|
325
|
+
* Where the pending authorization request (`state` → PKCE verifier,
|
|
326
|
+
* return-to) lives, resolved per request. Defaults to the client's
|
|
327
|
+
* configured `authRequestStorage`, which is in-memory unless the client was
|
|
328
|
+
* given something else — fine for a single long-lived process, wrong for any
|
|
329
|
+
* platform that can serve `/auth/login` and `/auth/callback` from different
|
|
330
|
+
* isolates. Pass an {@link EncryptedCookieAuthRequestStorage} to carry the
|
|
331
|
+
* record in an encrypted cookie instead, or implement
|
|
332
|
+
* {@link AuthRequestStorageFactory} over storage of your own.
|
|
333
|
+
*
|
|
334
|
+
* Whatever this resolves to, the pending request is **also** bound to the
|
|
335
|
+
* browser that started it by the login-state cookie, which no option turns
|
|
336
|
+
* off. See {@link loginStateTtlMs}.
|
|
337
|
+
*/
|
|
338
|
+
authRequestStorage?: AuthRequestStorageFactory;
|
|
339
|
+
/**
|
|
340
|
+
* How long, in ms, the browser keeps the login-state cookie `/auth/login`
|
|
341
|
+
* sets and `/auth/callback` requires — the `Max-Age` on that cookie, and so
|
|
342
|
+
* the window a started sign-in can still be completed in. Defaults to 10
|
|
343
|
+
* minutes, matching `DirectClient`'s `authRequestTtlMs` default; raise both
|
|
344
|
+
* together, since whichever expires first ends the pending sign-in.
|
|
345
|
+
*
|
|
346
|
+
* The cookie holds only SHA-256 hashes of the `state` values (the three most
|
|
347
|
+
* recent, so parallel sign-ins in several tabs all stay completable), is
|
|
348
|
+
* `HttpOnly`, `SameSite=Lax` — the strictest value that survives the
|
|
349
|
+
* identity provider's redirect back — and inherits {@link cookie}'s
|
|
350
|
+
* `secure` / `path` / `domain` so plain-HTTP local development works.
|
|
351
|
+
*/
|
|
352
|
+
loginStateTtlMs?: number;
|
|
353
|
+
/**
|
|
354
|
+
* Who owns the BFF's session record. Defaults to `"own"`.
|
|
355
|
+
*
|
|
356
|
+
* - `"own"` (default): the BFF creates and owns its session at
|
|
357
|
+
* `/auth/callback` — it always mints a fresh id (`sessionStore.create`)
|
|
358
|
+
* and ignores any inbound cookie. Use when the app has no session here
|
|
359
|
+
* until the OAuth flow completes: standalone BFFs, or apps whose login
|
|
360
|
+
* lives on a separate / external IDP.
|
|
361
|
+
* - `"shared"`: the app already created the session at its own login step,
|
|
362
|
+
* and the BFF **attaches** tokens to that existing session at
|
|
363
|
+
* `/auth/callback` (via `sessionStore.update`) instead of creating its
|
|
364
|
+
* own. Set {@link HonoBffCookieOptions.name} to the app's session-cookie
|
|
365
|
+
* name and back `sessionStore` with the app's session record. Use for a
|
|
366
|
+
* colocated first-party SPA that is also its own IDP, so the user has
|
|
367
|
+
* **one** session (login + tokens on one record), not two. If no session
|
|
368
|
+
* cookie is present at the callback the BFF falls back to creating one.
|
|
369
|
+
* `/auth/logout` destroys the whole shared session (a full sign-out).
|
|
370
|
+
* The app's cookie stays the app's: the BFF emits `Set-Cookie` only when
|
|
371
|
+
* the store hands back a **different** value (a stateless store does on
|
|
372
|
+
* every write), never to re-stamp an unchanged one — re-stamping would
|
|
373
|
+
* replace the attributes the app chose, turning a remember-me cookie
|
|
374
|
+
* with a `Max-Age` into a session cookie the browser drops on exit.
|
|
375
|
+
*
|
|
376
|
+
* Note on session fixation: this is the standard "renew the session id on
|
|
377
|
+
* authentication" control. In `"own"` mode the BFF performs the renewal
|
|
378
|
+
* (fresh id at the callback). In `"shared"` mode that responsibility sits
|
|
379
|
+
* with your login step — use a login that mints a fresh session id on
|
|
380
|
+
* success (the typical behavior, e.g. a session service that generates a
|
|
381
|
+
* new id per login) so a pre-planted cookie can't survive authentication.
|
|
382
|
+
*/
|
|
383
|
+
sessionMode?: "own" | "shared";
|
|
384
|
+
/** Cookie attribute tweaks. */
|
|
385
|
+
cookie?: HonoBffCookieOptions;
|
|
386
|
+
/**
|
|
387
|
+
* CSRF defense for the credentialed surface. Enabled by default with a
|
|
388
|
+
* `"x-csrf"` header requirement; pass `false` to disable, or an object to
|
|
389
|
+
* customize the header. See {@link HonoBffCsrfOptions}.
|
|
390
|
+
*/
|
|
391
|
+
csrf?: HonoBffCsrfOptions | false;
|
|
392
|
+
/**
|
|
393
|
+
* OIDC RP-Initiated Logout. When `true` **and** the configured client knows
|
|
394
|
+
* an end-session endpoint (`endpoints.endSession`, or via discovery),
|
|
395
|
+
* `/auth/logout` — after destroying the local session — additionally
|
|
396
|
+
* redirects the browser to the OP's `end_session_endpoint` with
|
|
397
|
+
* `id_token_hint` (the session's id_token, if any) and a
|
|
398
|
+
* `post_logout_redirect_uri` built from the resolved origin + the
|
|
399
|
+
* open-redirect-guarded `return_to`. This ends the upstream IDP SSO session,
|
|
400
|
+
* not just the local BFF session.
|
|
401
|
+
*
|
|
402
|
+
* Defaults to `false` (local logout only): not every IDP supports it, and it
|
|
403
|
+
* adds a redirect hop. When enabled but no end-session endpoint is known,
|
|
404
|
+
* logout falls back to the plain local redirect and warns once via
|
|
405
|
+
* `console.warn` — a client built from an explicit `endpoints` object with no
|
|
406
|
+
* `endSession` and no `issuer` can never learn one by discovery, so the
|
|
407
|
+
* feature would otherwise no-op silently.
|
|
408
|
+
*/
|
|
409
|
+
rpInitiatedLogout?: boolean;
|
|
410
|
+
/**
|
|
411
|
+
* OIDC Back-Channel Logout. When set, `routes()` mounts a
|
|
412
|
+
* `POST /auth/backchannel` receiver that ends the matching server-side
|
|
413
|
+
* session(s) when the OP notifies it. See {@link HonoBffBackchannelOptions}.
|
|
414
|
+
*/
|
|
415
|
+
backchannelLogout?: HonoBffBackchannelOptions;
|
|
416
|
+
/** Endpoint paths (within whatever base the caller mounts this under). */
|
|
417
|
+
paths?: HonoBffPaths;
|
|
418
|
+
/**
|
|
419
|
+
* Scope string requested at `/auth/login`, and the scope the login flow
|
|
420
|
+
* records for the authorization request it started.
|
|
421
|
+
*
|
|
422
|
+
* Server-chosen: a browser cannot change it by putting `scope` on the login
|
|
423
|
+
* URL. An application that wants the browser to choose lists `scope` in
|
|
424
|
+
* {@link forwardedParams}, and a forwarded value then **replaces** this one
|
|
425
|
+
* rather than narrowing it. An empty forwarded value counts as none
|
|
426
|
+
* supplied, so this value stands: a login link ending `?scope=` would
|
|
427
|
+
* otherwise send no `scope` at all, leaving an authorization server that
|
|
428
|
+
* defaults an absent scope to pick the grant instead.
|
|
429
|
+
*/
|
|
430
|
+
scope?: string;
|
|
431
|
+
/**
|
|
432
|
+
* Extra parameters added to every authorize request `/auth/login` builds —
|
|
433
|
+
* `login_hint`, `ui_locales`, `acr_values`, or whatever else the
|
|
434
|
+
* authorization server accepts, such as a parameter naming the organization
|
|
435
|
+
* a sign-in should land in.
|
|
436
|
+
*
|
|
437
|
+
* Server-chosen: these values are fixed when the BFF is constructed, so they
|
|
438
|
+
* carry the same trust as {@link scope} and nothing a browser sends can
|
|
439
|
+
* change them. When the browser is the one that should choose a parameter's
|
|
440
|
+
* value, name it in {@link forwardedParams} instead.
|
|
441
|
+
*
|
|
442
|
+
* `prompt` is an ordinary name here — pin `{ prompt: "login" }` to make
|
|
443
|
+
* every sign-in re-authenticate. A pinned `scope` is accepted only when
|
|
444
|
+
* {@link scope} is unset; setting both would leave which one applies
|
|
445
|
+
* ambiguous. Either configuration supplies the scope the login flow records
|
|
446
|
+
* for its authorization request.
|
|
447
|
+
*
|
|
448
|
+
* A name the login flow reserves is refused at construction rather than
|
|
449
|
+
* quietly dropped — {@link forwardedParams} carries the list and the reason.
|
|
450
|
+
*
|
|
451
|
+
* @example Pin every sign-in to one organization
|
|
452
|
+
* ```ts
|
|
453
|
+
* declare const client: DirectClient;
|
|
454
|
+
*
|
|
455
|
+
* const bff = new HonoBff({ client, extraParams: { organization: "acme" } });
|
|
456
|
+
* ```
|
|
457
|
+
*/
|
|
458
|
+
extraParams?: Record<string, string>;
|
|
459
|
+
/**
|
|
460
|
+
* Names of extra authorize parameters `/auth/login` may take off its own
|
|
461
|
+
* query string. Empty by default, so a parameter that is not listed here is
|
|
462
|
+
* not forwarded no matter who puts it on the login URL — `scope` and
|
|
463
|
+
* `prompt` included. `routes()` serves login at `GET` and `POST` alike, and
|
|
464
|
+
* both read the query string only, never a request body.
|
|
465
|
+
*
|
|
466
|
+
* Browser-chosen, and so untrusted: whatever a browser puts in
|
|
467
|
+
* `/auth/login?<name>=…` reaches the authorization server verbatim, and the
|
|
468
|
+
* BFF checks nothing but the name. The test to apply is not "does the
|
|
469
|
+
* authorization server validate this parameter" but **"would I accept every
|
|
470
|
+
* value it could carry"** — the two are different properties, and a
|
|
471
|
+
* parameter can pass the first and fail the second. `acr_values` and
|
|
472
|
+
* `max_age` are refused here, case-insensitively, because browser choices
|
|
473
|
+
* could weaken assurance or lengthen the re-authentication window. Pin these
|
|
474
|
+
* controls in {@link extraParams}, which no browser can change.
|
|
475
|
+
*
|
|
476
|
+
* A parameter already baked into the configured authorization endpoint's
|
|
477
|
+
* own query string is overridable this way too, and silently: the client
|
|
478
|
+
* keeps that query string and then sets each extra parameter over it. The
|
|
479
|
+
* reserved list below cannot know a name a deployment baked into its
|
|
480
|
+
* endpoint URL, so check that URL before listing anything here.
|
|
481
|
+
*
|
|
482
|
+
* Reserved on this option and on {@link extraParams} alike, and refused when
|
|
483
|
+
* the BFF is constructed — matched case-insensitively, since an
|
|
484
|
+
* authorization server that folds parameter-name case would otherwise make
|
|
485
|
+
* `Client_Id` a way around the list:
|
|
486
|
+
*
|
|
487
|
+
* - `response_type`, `client_id`, `redirect_uri`, `state`,
|
|
488
|
+
* `code_challenge`, `code_challenge_method` — the client mints or derives
|
|
489
|
+
* each one for the single sign-in in progress, and a supplied value
|
|
490
|
+
* replaces it, which variously redirects the authorization code, breaks
|
|
491
|
+
* the PKCE binding the token exchange depends on, or unpicks the `state`
|
|
492
|
+
* the login-state cookie commits to.
|
|
493
|
+
* - `response_mode` — moves the authorization response out of the callback
|
|
494
|
+
* URL's query string, the only place `/auth/callback` reads it from.
|
|
495
|
+
* - `nonce` — this client neither sends nor verifies one, so a supplied
|
|
496
|
+
* value would look like replay protection that nothing checks.
|
|
497
|
+
* - `request`, `request_uri` — a request object supersedes the parameters
|
|
498
|
+
* beside it at an authorization server implementing RFC 9101, which puts
|
|
499
|
+
* every name above back within reach.
|
|
500
|
+
*
|
|
501
|
+
* Naming the same parameter both here and in {@link extraParams} is refused
|
|
502
|
+
* as well: one pins the value every sign-in carries and the other lets the
|
|
503
|
+
* browser choose it, so which one applies would be ambiguous.
|
|
504
|
+
*
|
|
505
|
+
* `scope` and `prompt` are ordinary names on this option, and the two worth
|
|
506
|
+
* the most thought before listing. Listing `scope` lets whoever writes the
|
|
507
|
+
* login link choose the breadth of the grant the session is minted with:
|
|
508
|
+
* the value replaces {@link scope} rather than narrowing it, bounded only
|
|
509
|
+
* by what the authorization server will grant this client. An empty `scope`
|
|
510
|
+
* is the one exception, and counts as absent so {@link scope} stands.
|
|
511
|
+
* Listing `prompt` hands over the authentication ceremony instead of the
|
|
512
|
+
* grant — it is what a SPA needs to start a silent renew with
|
|
513
|
+
* `/auth/login?prompt=none`, and the same entry lets a link suppress a
|
|
514
|
+
* re-authentication the application meant to force. Where the deployment should decide either one, use
|
|
515
|
+
* {@link scope} or a pinned {@link extraParams} entry, which no browser can
|
|
516
|
+
* reach.
|
|
517
|
+
*
|
|
518
|
+
* @example Let a sign-in link choose the organization
|
|
519
|
+
* ```ts
|
|
520
|
+
* declare const client: DirectClient;
|
|
521
|
+
*
|
|
522
|
+
* const bff = new HonoBff({ client, forwardedParams: ["organization"] });
|
|
523
|
+
* ```
|
|
524
|
+
*
|
|
525
|
+
* A link to `/auth/login?organization=acme&return_to=/dashboard` then
|
|
526
|
+
* reaches the authorization server as `…&organization=acme`.
|
|
527
|
+
*/
|
|
528
|
+
forwardedParams?: readonly string[];
|
|
529
|
+
/** Default `return_to` applied when the browser doesn't pass one. */
|
|
530
|
+
defaultReturnTo?: string;
|
|
531
|
+
/**
|
|
532
|
+
* Optional hook run after the authorization code is exchanged. Lets
|
|
533
|
+
* apps enrich the session with database-backed user info before the
|
|
534
|
+
* session cookie is set.
|
|
535
|
+
*/
|
|
536
|
+
resolveUser?: (tokens: TokenBundle, idTokenClaims: UserInfoClaims | null) => Promise<UserInfoClaims | null> | UserInfoClaims | null;
|
|
537
|
+
/**
|
|
538
|
+
* Optional hook invoked when `/auth/callback` cannot complete the sign-in
|
|
539
|
+
* (see {@link BffCallbackError} for the cases). The browser reaches the
|
|
540
|
+
* callback via a full-page navigation, so the default JSON `400` is not
|
|
541
|
+
* user-friendly — return a redirect to a sign-in/error page instead, e.g.
|
|
542
|
+
* `c.redirect(`/sign-in?error=${err.error}`)`. When omitted, the BFF responds
|
|
543
|
+
* with a `400` JSON body `{ error, error_description }`.
|
|
544
|
+
*/
|
|
545
|
+
onCallbackError?: (c: Context, error: BffCallbackError) => Response | Promise<Response>;
|
|
546
|
+
/**
|
|
547
|
+
* Clock skew (seconds) applied when deciding whether the access token
|
|
548
|
+
* is close enough to expiry that {@link HonoBff.attachToken} should
|
|
549
|
+
* refresh proactively. Defaults to 30s.
|
|
550
|
+
*/
|
|
551
|
+
refreshSkewSeconds?: number;
|
|
552
|
+
/**
|
|
553
|
+
* Optional resource server. When provided, {@link HonoBff.protect}
|
|
554
|
+
* becomes available — a single middleware that composes
|
|
555
|
+
* {@link HonoBff.attachToken} with `resourceServer.protect(scope)`.
|
|
556
|
+
*
|
|
557
|
+
* Pass a {@link HonoAuthorizationServer} for own-issuer co-located
|
|
558
|
+
* deployments (validation happens against the in-process token service)
|
|
559
|
+
* or a {@link HonoResourceServer} for third-party-issuer deployments
|
|
560
|
+
* (validation goes through `IntrospectionTokenReader`).
|
|
561
|
+
*/
|
|
562
|
+
resourceServer?: HonoBffResourceServer;
|
|
563
|
+
/**
|
|
564
|
+
* Resolves the per-request origin (scheme + host) used to build the
|
|
565
|
+
* browser-facing authorize URL and `redirect_uri`. Lets ONE build-once BFF
|
|
566
|
+
* serve every deployment host (preview URLs, multi-tenant subdomains): the
|
|
567
|
+
* configured client's authorize/redirect paths are retargeted at this
|
|
568
|
+
* origin per request. Return only allowlisted/trusted origins (never a raw
|
|
569
|
+
* forged `Host`). When omitted, the client's configured origin is used.
|
|
570
|
+
*/
|
|
571
|
+
resolveOrigin?: (c: Context) => string;
|
|
572
|
+
/**
|
|
573
|
+
* Derive the OAuth2 `redirect_uri` from the mounted callback path instead of
|
|
574
|
+
* requiring it in the client config. When `true`, every login/callback uses
|
|
575
|
+
* `redirect_uri = <origin><callback path>` (origin from {@link resolveOrigin},
|
|
576
|
+
* else the request's). The same value is sent at authorize and at the token
|
|
577
|
+
* exchange (RFC 6749 §4.1.3), re-derived from the request each time. Pairs
|
|
578
|
+
* with an `issuer`-configured client (discovery) for a BFF you configure by
|
|
579
|
+
* issuer alone — no restated endpoints or redirect URI. Defaults to `false`
|
|
580
|
+
* (use the client's configured `redirectUri`).
|
|
581
|
+
*/
|
|
582
|
+
deriveRedirectUri?: boolean;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Backend-for-Frontend adapter for Hono. Construct once per app, mount
|
|
586
|
+
* `routes()` under `/auth`, and use `attachToken()` as middleware on
|
|
587
|
+
* protected API routes. Apps that own their login surface (a custom sign-in
|
|
588
|
+
* form, create-account, forgot-password) call `loginContinuation()` to hand
|
|
589
|
+
* back to the OAuth2 flow after authenticating a user.
|
|
590
|
+
*/
|
|
591
|
+
export declare class HonoBff {
|
|
592
|
+
#private;
|
|
593
|
+
/**
|
|
594
|
+
* Defaults are applied for the session store ({@linkcode MemorySessionStore}),
|
|
595
|
+
* session mode (`"own"`), cookie name, and CSRF.
|
|
596
|
+
*
|
|
597
|
+
* @throws {Error} When {@linkcode HonoBffOptions.backchannelLogout} is
|
|
598
|
+
* configured against a store that cannot enumerate sessions (e.g.
|
|
599
|
+
* {@linkcode EncryptedCookieSessionStore}), when an explicit
|
|
600
|
+
* {@linkcode HonoBffCookieOptions.name} carries a `__Host-`/`__Secure-`
|
|
601
|
+
* prefix the other cookie options contradict, when
|
|
602
|
+
* {@linkcode HonoBffCookieOptions.sameSite} is `"None"` without
|
|
603
|
+
* {@linkcode HonoBffCookieOptions.secure}, when any
|
|
604
|
+
* {@linkcode HonoBffPaths} entry falls outside a non-empty
|
|
605
|
+
* {@linkcode HonoBffPaths.basePath}, when the session cookie would die
|
|
606
|
+
* before the session behind it (see
|
|
607
|
+
* {@linkcode HonoBffOptions.sessionMaxAgeMs}), or when
|
|
608
|
+
* {@linkcode HonoBffOptions.extraParams} or
|
|
609
|
+
* {@linkcode HonoBffOptions.forwardedParams} names a reserved authorize
|
|
610
|
+
* parameter, gives one a name that is empty or not equal to its trimmed
|
|
611
|
+
* form (`" state"` and `"organization "` are refused rather than trimmed),
|
|
612
|
+
* names the same parameter in both, or pins `scope` in
|
|
613
|
+
* {@linkcode HonoBffOptions.extraParams} while
|
|
614
|
+
* {@linkcode HonoBffOptions.scope} is also set.
|
|
615
|
+
*/
|
|
616
|
+
constructor(options: HonoBffOptions);
|
|
617
|
+
/**
|
|
618
|
+
* Cookie name used for the session cookie. Read-only; configured via
|
|
619
|
+
* {@link HonoBffOptions.cookie}.
|
|
620
|
+
*/
|
|
621
|
+
get cookieName(): string;
|
|
622
|
+
/**
|
|
623
|
+
* Name of the cookie `/auth/login` binds the pending sign-in's `state` to
|
|
624
|
+
* and `/auth/callback` requires. Read-only; derived from
|
|
625
|
+
* {@link HonoBffOptions.cookie}'s `secure` / `path` / `domain` (it carries
|
|
626
|
+
* the `__Host-` prefix whenever they allow it). A test that drives the
|
|
627
|
+
* login flow by hand must carry this cookie from the login response into
|
|
628
|
+
* the callback request, exactly as a browser would.
|
|
629
|
+
*/
|
|
630
|
+
get loginStateCookieName(): string;
|
|
631
|
+
/**
|
|
632
|
+
* The CSRF header name required on credentialed requests, or `undefined`
|
|
633
|
+
* when CSRF is disabled. Useful in tests that seed a session and then call a
|
|
634
|
+
* protected route — they must send `{ [bff.csrfHeaderName!]: "1" }` alongside
|
|
635
|
+
* the cookie. The shipped `BffClient` / React adapter send it
|
|
636
|
+
* automatically in real apps.
|
|
637
|
+
*/
|
|
638
|
+
get csrfHeaderName(): string | undefined;
|
|
639
|
+
/**
|
|
640
|
+
* The underlying session store. Exposed primarily for testing helpers
|
|
641
|
+
* (e.g. seeding a session directly without driving the login flow);
|
|
642
|
+
* production code should not reach in here.
|
|
643
|
+
*/
|
|
644
|
+
get sessionStore(): SessionStore;
|
|
645
|
+
/**
|
|
646
|
+
* Returns a Hono app with all session endpoints mounted, each registered
|
|
647
|
+
* relative to {@link HonoBffPaths.basePath} (default `/auth`). Mount it under
|
|
648
|
+
* that same base — `app.route("/auth", bff.routes())` — so the served URLs
|
|
649
|
+
* are the configured {@link HonoBffPaths}. To mount at the app root instead,
|
|
650
|
+
* set `paths.basePath` to `""`.
|
|
651
|
+
*/
|
|
652
|
+
routes(): Hono;
|
|
653
|
+
/**
|
|
654
|
+
* Builds the URL that (re)starts sign-in toward `returnTo`, for an app that
|
|
655
|
+
* owns its login surface (a custom sign-in form, create-account,
|
|
656
|
+
* forgot-password) and needs to hand back to the OAuth2/BFF flow after it
|
|
657
|
+
* authenticates the user.
|
|
658
|
+
*
|
|
659
|
+
* - When `returnTo` is an **in-flight authorize URL** — its path matches the
|
|
660
|
+
* client's authorization endpoint, i.e. the user was bounced to your login
|
|
661
|
+
* surface mid-authorize with the PKCE `state` already minted — it is
|
|
662
|
+
* returned unchanged so the flow **resumes** without a redundant
|
|
663
|
+
* `/auth/login` hop.
|
|
664
|
+
* - Otherwise it returns `${login}?return_to=<returnTo>` so the BFF **starts**
|
|
665
|
+
* a fresh login (mints `state` at the login endpoint, attaches tokens to the
|
|
666
|
+
* session at the callback).
|
|
667
|
+
*
|
|
668
|
+
* `returnTo` is open-redirect-guarded (`safeReturnTo`), so the result is
|
|
669
|
+
* always a safe same-origin path. This is the mechanism of the own-auth + BFF
|
|
670
|
+
* topology; the credential form, validation, and copy stay in your app. It
|
|
671
|
+
* replaces hand-rolled helpers that hardcode the authorize + login paths —
|
|
672
|
+
* the BFF already knows both from its client config and `paths`.
|
|
673
|
+
*
|
|
674
|
+
* ```ts
|
|
675
|
+
* // after your sign-in / create-account action authenticates the user:
|
|
676
|
+
* return c.redirect(bff.loginContinuation(returnTo));
|
|
677
|
+
* ```
|
|
678
|
+
*
|
|
679
|
+
* Note: the in-flight-authorize check needs the client's authorization
|
|
680
|
+
* endpoint to be known synchronously (an explicit `endpoints.authorization`,
|
|
681
|
+
* or after `client.discover()` has run). For an issuer-only client that has
|
|
682
|
+
* not discovered yet, every `returnTo` is treated as "start a fresh login".
|
|
683
|
+
*/
|
|
684
|
+
loginContinuation(returnTo: string | null | undefined): string;
|
|
685
|
+
/**
|
|
686
|
+
* Login handler — builds the authorize URL, binds the `state` it minted to
|
|
687
|
+
* this browser with the login-state cookie, and redirects the browser.
|
|
688
|
+
*
|
|
689
|
+
* The authorize request carries every {@link HonoBffOptions.extraParams}
|
|
690
|
+
* entry, plus each {@link HonoBffOptions.forwardedParams} name this
|
|
691
|
+
* request's query string supplies a value for. Nothing else off this
|
|
692
|
+
* request's query string reaches the authorize URL: `scope` falls back to
|
|
693
|
+
* {@link HonoBffOptions.scope} and `prompt` is sent only when one of those
|
|
694
|
+
* two options supplies it.
|
|
695
|
+
*/
|
|
696
|
+
loginHandler(): Handler;
|
|
697
|
+
/**
|
|
698
|
+
* Callback handler — requires the login-state cookie to vouch for the
|
|
699
|
+
* returned `state`, then exchanges the code and opens a session.
|
|
700
|
+
*/
|
|
701
|
+
callbackHandler(): Handler;
|
|
702
|
+
/** Logout handler — revokes the refresh token and clears the session cookie. */
|
|
703
|
+
logoutHandler(): Handler;
|
|
704
|
+
/** Session probe — returns `{ isAuthenticated, user }` without tokens. */
|
|
705
|
+
sessionHandler(): Handler;
|
|
706
|
+
/**
|
|
707
|
+
* OIDC Back-Channel Logout receiver. A server-to-server `POST` from the OP
|
|
708
|
+
* carrying a signed `logout_token`; the configured verifier validates it (and
|
|
709
|
+
* returns the subject / OP session id), then the matching server-side
|
|
710
|
+
* session(s) are destroyed. Responds `200` on success and `400` on a
|
|
711
|
+
* missing/invalid token, with `Cache-Control: no-store`, per the spec.
|
|
712
|
+
*/
|
|
713
|
+
backchannelHandler(): Handler;
|
|
714
|
+
/**
|
|
715
|
+
* Middleware that reads the session cookie, refreshes the access token
|
|
716
|
+
* if it's near expiry, and sets `Authorization: Bearer <token>` on the
|
|
717
|
+
* inbound request so downstream middleware can read it from the
|
|
718
|
+
* standard header.
|
|
719
|
+
*
|
|
720
|
+
* Prefer {@link protect} for the common case — it authenticates against
|
|
721
|
+
* the configured `resourceServer` directly without mutating the
|
|
722
|
+
* request, so middleware ordering doesn't matter. Reach for
|
|
723
|
+
* `attachToken` only when composing with downstream middleware that
|
|
724
|
+
* expects a bearer token in the `Authorization` header (e.g. a custom
|
|
725
|
+
* resource-server-like guard, or an in-process proxy).
|
|
726
|
+
*
|
|
727
|
+
* **Ordering caveat**: mount `attachToken` *before* any middleware
|
|
728
|
+
* that reads the request body (CSRF parsers, validators, etc.).
|
|
729
|
+
* `attachToken` replaces `c.req.raw` with a clone that carries the
|
|
730
|
+
* Authorization header, which transfers the body stream from the
|
|
731
|
+
* original Request to the clone. Downstream middleware that reads
|
|
732
|
+
* through `c.req` is fine; middleware that captured a pre-clone
|
|
733
|
+
* reference to `c.req.raw` will see a "used" body on it.
|
|
734
|
+
*
|
|
735
|
+
* Unauthenticated requests fall through without a header — the
|
|
736
|
+
* downstream guard returns the proper 401 with `WWW-Authenticate`.
|
|
737
|
+
*/
|
|
738
|
+
attachToken(): MiddlewareHandler;
|
|
739
|
+
/**
|
|
740
|
+
* Returns a Hono middleware that asserts an **already-authenticated**
|
|
741
|
+
* request (guarded upstream by {@link protect}) carries `requiredScope`,
|
|
742
|
+
* **without** re-validating the token — the per-route companion to
|
|
743
|
+
* `protect` for mounts whose routes need different scopes (e.g. `read` vs
|
|
744
|
+
* `write`). Delegates to the configured resource server's `requireScope`;
|
|
745
|
+
* see {@link HonoResourceServer.requireScope}.
|
|
746
|
+
*
|
|
747
|
+
* @throws {Error} When the BFF was constructed without a `resourceServer` —
|
|
748
|
+
* thrown when the middleware is built, not per request.
|
|
749
|
+
*
|
|
750
|
+
* @example
|
|
751
|
+
* ```ts
|
|
752
|
+
* app.use("/api/*", bff.protect()); // session cookie OR bearer
|
|
753
|
+
* app.get("/api/items", bff.requireScope("read"), list);
|
|
754
|
+
* app.post("/api/items", bff.requireScope("write"), create);
|
|
755
|
+
* ```
|
|
756
|
+
*/
|
|
757
|
+
requireScope(requiredScope: string): MiddlewareHandler;
|
|
758
|
+
/**
|
|
759
|
+
* Middleware that authenticates the request against the configured
|
|
760
|
+
* `resourceServer`, accepting **either** auth style so one guard protects an
|
|
761
|
+
* API used by both the BFF's own frontend and machine-to-machine clients:
|
|
762
|
+
*
|
|
763
|
+
* - **Browser frontend** — no `Authorization` header; the access token is
|
|
764
|
+
* resolved from the BFF session cookie (refreshed if near expiry) and
|
|
765
|
+
* validated. Available only when the BFF was constructed with a
|
|
766
|
+
* `resourceServer` option.
|
|
767
|
+
* - **Machine-to-machine** — an inbound `Authorization: Bearer <token>` is
|
|
768
|
+
* validated directly (e.g. a `client_credentials` token), so direct API
|
|
769
|
+
* clients work without a session cookie.
|
|
770
|
+
*
|
|
771
|
+
* Unlike `attachToken + resourceServer.protect()`, this does **not**
|
|
772
|
+
* mutate `c.req.raw` for the session path — the token is read from the
|
|
773
|
+
* session and handed to `resourceServer.authenticate()` directly, so
|
|
774
|
+
* middleware ordering doesn't matter and earlier middleware that already
|
|
775
|
+
* touched the request body is unaffected.
|
|
776
|
+
*
|
|
777
|
+
* ```ts
|
|
778
|
+
* app.use("/api/*", bff.protect("read")); // session cookie OR bearer token
|
|
779
|
+
* ```
|
|
780
|
+
*
|
|
781
|
+
* Without `resourceServer` configured, throws an error with a hint to
|
|
782
|
+
* either pass `resourceServer` or compose the middleware chain manually
|
|
783
|
+
* (`app.use(bff.attachToken(), resourceServer.protect(scope))`).
|
|
784
|
+
*/
|
|
785
|
+
protect(requiredScope?: string): MiddlewareHandler;
|
|
786
|
+
/**
|
|
787
|
+
* Handler that forwards the request to a **separate** resource server with
|
|
788
|
+
* the session's access token attached, and streams the response back — the
|
|
789
|
+
* full-proxy BFF of the IETF "OAuth 2.0 for Browser-Based Apps" BCP §6.1.1.
|
|
790
|
+
* Tokens stay server-side and the browser only ever talks to its own origin,
|
|
791
|
+
* so there is no CORS to configure.
|
|
792
|
+
*
|
|
793
|
+
* Reach for this when the API lives in another service. When the resource
|
|
794
|
+
* server is co-located in the same process, {@link protect} is faster and
|
|
795
|
+
* simpler — it validates in-process with no outbound HTTP.
|
|
796
|
+
*
|
|
797
|
+
* Mount it as a catch-all and strip the mount prefix:
|
|
798
|
+
*
|
|
799
|
+
* @example
|
|
800
|
+
* ```ts
|
|
801
|
+
* app.all(
|
|
802
|
+
* "/api/*",
|
|
803
|
+
* bff.proxy("https://api.example.com/v1", { stripPrefix: "/api" }),
|
|
804
|
+
* );
|
|
805
|
+
* ```
|
|
806
|
+
*
|
|
807
|
+
* Semantics:
|
|
808
|
+
*
|
|
809
|
+
* - **Session only.** The access token comes from the session cookie
|
|
810
|
+
* (refreshed when within {@link HonoBffOptions.refreshSkewSeconds} of
|
|
811
|
+
* expiry, exactly as {@link protect} does). An inbound `Authorization`
|
|
812
|
+
* header is *not* honored and never forwarded — point machine-to-machine
|
|
813
|
+
* clients at the resource server directly. No session is `401`
|
|
814
|
+
* `invalid_token`.
|
|
815
|
+
* - **CSRF.** The same custom-header check the rest of the credentialed BFF
|
|
816
|
+
* surface enforces, so mounting a proxy cannot reopen it. See
|
|
817
|
+
* {@link HonoBffCsrfOptions}.
|
|
818
|
+
* - **Retry.** An upstream `401` carrying `error="invalid_token"` triggers
|
|
819
|
+
* one refresh-and-retry, when the session has a refresh token. A refresh
|
|
820
|
+
* the authorization server rejects clears the session and answers `401`; a
|
|
821
|
+
* refresh that merely fails to complete (network error, 5xx, 429) leaves
|
|
822
|
+
* the session intact and answers `502` `temporarily_unavailable`. Bodied
|
|
823
|
+
* requests are not retried (the body was streamed). Disable with
|
|
824
|
+
* `retryOn401: false`.
|
|
825
|
+
* - **Headers.** Only {@link DEFAULT_PROXY_FORWARD_HEADERS} (or your
|
|
826
|
+
* `forwardHeaders`) travel upstream; the session cookie, inbound
|
|
827
|
+
* `Authorization`, and hop-by-hop headers never do. Coming back, upstream
|
|
828
|
+
* `Set-Cookie` and hop-by-hop headers are dropped and everything else —
|
|
829
|
+
* including problem-details error bodies and their status — is passed
|
|
830
|
+
* through verbatim.
|
|
831
|
+
* - **Bodies stream** in both directions; nothing is buffered. Upstream
|
|
832
|
+
* redirects are returned to the browser rather than followed, so the token
|
|
833
|
+
* is never replayed to a `Location` the BFF did not vet.
|
|
834
|
+
*
|
|
835
|
+
* A `502` with `temporarily_unavailable` is returned when the upstream
|
|
836
|
+
* cannot be reached, and a `400` `invalid_request` when a path segment has a
|
|
837
|
+
* malformed percent-escape or hides a `..`.
|
|
838
|
+
*
|
|
839
|
+
* **Ordering caveat**: mount this before any middleware that reads the
|
|
840
|
+
* request body — the proxy forwards `c.req.raw.body` as a stream, and a
|
|
841
|
+
* middleware that already consumed it leaves nothing to forward.
|
|
842
|
+
*
|
|
843
|
+
* @param target Upstream base URL. Fixed here, never derived from the
|
|
844
|
+
* request, so the proxy cannot be steered at another host. Query parameters
|
|
845
|
+
* pinned on it are merged into every proxied request and win over an inbound
|
|
846
|
+
* parameter of the same name.
|
|
847
|
+
* @param options Prefix stripping, header allowlist, retry, and `fetch`
|
|
848
|
+
* injection. See {@link HonoBffProxyOptions}.
|
|
849
|
+
* @returns A Hono handler to mount on a catch-all route.
|
|
850
|
+
*/
|
|
851
|
+
proxy(target: string | URL, options?: HonoBffProxyOptions): Handler;
|
|
852
|
+
}
|