Repository navigation
feat(oauth-server-kit): add hardened Better Auth defaults - #35
Merged
Merged
Conversation
Products configure Better Auth themselves, so each one re-derived the same hardening. The kit now ships it as helpers products spread into their config: - secureCookieOptions / sessionCookieName: __Host- session cookies over https (__Secure- with cross-subdomain cookies, plain names over http), and Better Auth reads the client address only from a header the edge sets. - secureSessionOptions: no session cookie cache, so revoked sessions stop working on the next request. - secureEmailAndPasswordOptions: password sign-up off; opting in also requires a verified email before sign-in. - authenticatedAt on session identities and isRecentlyAuthenticated for step-up checks. - Express: parseTrustProxy and createClientIpMiddleware, which resolve the client address from a trusted proxy chain instead of the leftmost X-Forwarded-For entry. The reference product and template use the helpers. ADR 0018 records the decision. Under __Host- the OAuth provider's RP-initiated logout confirmation cookie is rejected by browsers (the plugin fixes its path); this is documented.
Better Auth 1.7.16 fixes advisories that touch the kit's defaults: revoked sessions outliving the cookie cache on sensitive routes (GHSA-fvxx-76fc-893x), email OTP attempt-limit races (GHSA-2f9c-7xj7-gh55) and unverified OAuth emails counting as verified (GHSA-9hm7-xhfp-cx58). - Peer floor for better-auth and @better-auth/oauth-provider is ^1.7.16; dev, reference product, template and preset pins move to 1.7.22 with the matching @better-auth/utils 0.5.1, better-call 1.4.5 and @better-fetch/fetch 1.3.3 (a mismatched utils pin split @better-auth/core into two copies and broke plugin types). - The resource client type now matches newer oauth-provider: it needs the product's real Better Auth instance (options and a $context resolving baseURL). - Reference product: type the database option and drop account.issuer, which Better Auth stopped writing in 1.7.3 and 1.7.22 refuses at startup when required. - Docs: the default trusted proxies are private networks; set TRUST_PROXY to the real proxy subnet when clients can reach the app from a private network. ADR 0018 and the changeset record the floor and upgrade steps. The session cookie cache stays off by default: 1.7.16 only guards some routes, and Better Auth's advisory itself recommends disabling the cache when revocation must be immediate.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Every product configures Better Auth itself, so CourseLit, SendLit and MediaLit would each re-derive the same hardening. This moves the hardening CourseLit built locally into
@codelitdev/oauth-server-kitas helpers that products spread into their own config. The kit still doesn't create the auth instance.@codelitdev/oauth-server-kit/better-auth:secureCookieOptions({ baseURL, cookiePrefix, crossSubDomainCookies?, clientIpHeader? })chooses the cookie names:__Host-<prefix>session cookies (Secure,Path=/, noDomain), so a sibling subdomain can't plant one.__Secure-.clientIpHeader: Better Auth reads the client address only from that header, never fromX-Forwarded-For.sessionCookieName(advanced)returns the resulting session cookie name, for credential selection.secureSessionOptions()turns off the session cookie cache, so a revoked session stops working on the next request.secureEmailAndPasswordOptions()keeps password sign-up off. A product that opts in with{ enabled: true }also requires a verified email before sign-in.authenticatedAtandisRecentlyAuthenticated: session identities now carryauthenticatedAt, andisRecentlyAuthenticated(identity, { maxAgeMs })allows 60 seconds of clock skew, for "signed in recently" checks.@codelitdev/oauth-server-kit/express:parseTrustProxyandDEFAULT_TRUST_PROXY: the default is private networks.createClientIpMiddleware({ header })writesreq.ipinto the header and overwrites any value the client sent.req.ipreadsX-Forwarded-Forfrom the right, so the left-hand side, which an attacker controls, never counts.Also in this PR:
secureCookieOptions.Known limitation
Better Auth's OAuth-provider plugin sets its RP-initiated logout confirmation cookie on a fixed narrow path. A
__Host-cookie has to usePath=/, so browsers reject it, and that logout confirmation step stops working. Neither the plugin's options noradvanced.cookiescan change the path. This is documented, and products that need that step should use cross-subdomain mode.Product changes after release
__Host-renames the session cookie, so users sign in once more.sessionCookieName(...). The reference product needed this.Testing
check-types,biome lint .andformat:checkare clean.Better Auth 1.7.22
The second commit sets the kit's minimum Better Auth version to
^1.7.16. That release fixes GHSA-fvxx-76fc-893x, GHSA-2f9c-7xj7-gh55 and GHSA-9hm7-xhfp-cx58, which affect these defaults.@better-auth/utils(0.5.1),better-call(1.4.5) and@better-fetch/fetch(1.3.3) move with them. A mismatchedutilspin had installed two copies of@better-auth/core, which broke the plugin types.@better-auth/oauth-provider, which needs the product's real Better Auth instance.account.issueris dropped. Better Auth stopped writing it in 1.7.3, and 1.7.22 refuses to start if it is still required.TRUST_PROXYto the real proxy subnet when clients can reach the app from a private network.bun run verifyandtemplate:smokepass, along withformat:checkand lint.