Skip to content

refactor: add hardening to auth flows and jose operations#165

Merged
halvaradop merged 4 commits into
masterfrom
refactor/add-hardening
May 22, 2026
Merged

refactor: add hardening to auth flows and jose operations#165
halvaradop merged 4 commits into
masterfrom
refactor/add-hardening

Conversation

@halvaradop
Copy link
Copy Markdown
Member

@halvaradop halvaradop commented May 22, 2026

Description

This pull request introduces additional hardening across authentication flows in @aura-stack/auth and @aura-stack/jose to strengthen security guarantees, improve validation reliability, and reduce configuration risks.

The update includes security-focused improvements, bug fixes, and cryptographic enhancements designed to improve both runtime safety and developer experience.

Bug Fixes

  • Improved OAuth token error responses with clearer failure details
  • Fixed cookie clearing behavior in OAuth callback and session endpoints
  • Simplified successful session API payloads for get-session responses
  • Improved CSRF verification reliability and consistency

Security Improvements

  • Enforced stronger secret entropy requirements
  • Improved password verification and secret validation logic
  • Tightened trusted-origin and trusted-proxy validation
  • Strengthened redirect and header validation handling

Features

  • Enhanced cryptography support with broader asymmetric key compatibility
  • Added automatic signing and encryption algorithm inference based on the provided secret type

Key Changes

  • Fixed cookie management in OAuth callback and session flows
  • Improved CSRF verification logic
  • Enhanced algorithm inference based on the provided secret
  • Strengthened validation and hardening across authentication internals

@halvaradop halvaradop added the refactor Refactor without changing behavior label May 22, 2026
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
auth Skipped Skipped May 22, 2026 8:49pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

Warning

Rate limit exceeded

@halvaradop has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 43 minutes and 20 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a6f55058-ae59-4060-9923-ff59d674a1b8

📥 Commits

Reviewing files that changed from the base of the PR and between df07a97 and 5e8ae1e.

📒 Files selected for processing (3)
  • packages/jose/src/assert.ts
  • packages/jose/src/encrypt.ts
  • packages/jose/test/encrypt.test.ts
📝 Walkthrough

Walkthrough

Refactors AuthConfig and cookie helpers, reworks OAuth callback and access-token error paths, tightens crypto and secret-entropy checks, adds JOSE asymmetric algorithm inference and API rename, updates error stack-trace handling, and adjusts tests and docs.

Changes

Core + JOSE changes

Layer / File(s) Summary
Access token and callback header flow
packages/core/src/actions/callback/access-token.ts, packages/core/src/actions/callback/callback.ts
Access token catch now rethrows existing OAuthProtocolError and otherwise wraps failures; callback builds reusable cookie-clearing headers via getExpiredCookie(...) and returns a JSON 400 on state mismatch; success response reuses cleared headers and sets session/csrf cookies.
Userinfo mapping & authorization tests
packages/core/src/actions/callback/userinfo.ts, packages/core/src/actions/signIn/authorization.ts, packages/core/test/actions/signIn/authorization.test.ts
getDefaultUserInfo requires one of provider identifier fields for sub and throws on absence; import ordering adjusted; redirect-related tests expanded to cover trustedProxyHeaders/trustedOrigins cases.
Session retrieval and cookie clearing
packages/core/src/api/getSession.ts, packages/core/src/session/cookie-manager.ts
getSession and cookie-manager use getExpiredCookie(...) to expire cookies; successful getSession response omits previous error field.
Cookie helper & AuthConfig types
packages/core/src/cookie.ts, packages/core/src/@types/config.ts
Removes expiredCookieAttributes in favor of exported getExpiredCookie(options?); AuthConfig converted from interface to type and intersected with new TrustedProxyHeadersConfig discriminated union; cookie-name literals/docs updated to camelCase.
JOSE init/imports and algorithm inference
packages/core/src/jose.ts, packages/jose/src/encrypt.ts, packages/jose/src/sign.ts, packages/jose/src/index.ts, packages/jose/src/assert.ts
Rewires an import path and stops swallowing JOSE init errors; adds isCryptoKey predicate; encrypt/sign now infer RSA vs dir/HS algorithms from CryptoKey type; compact encrypt helper renamed to encryptCompactJWE and callers updated.
Secret entropy & assertions
packages/jose/src/secret.ts
Replaces getEntropy with assertSecretEntropy enforcing per-character and total entropy thresholds and exposes MIN_SECRET_ENTROPY_PER_CHAR/updated MIN_SECRET_ENTROPY_BITS.
Error stack-trace capture
packages/core/src/shared/errors.ts, packages/jose/src/errors.ts
Adds hasCaptureStackTrace type guard and conditionally captures stack traces in error constructors where runtime supports it.
Crypto, env, utils, validator
packages/core/src/shared/crypto.ts, packages/core/src/shared/env.ts, packages/core/src/shared/utils.ts, packages/core/src/validator/registry.ts
Tightens PBKDF2 typings; verifyPassword compares extracted hash segments with timing-safe compare; getEnv selects keys via !== undefined; patternToRegex rejects non-*. wildcard hosts; validator throws explicit AuthValidationError on unsupported schema branches.
Tests & changelog
packages/jose/test/*, packages/core/test/*, packages/jose/CHANGELOG.md
Adds/updates JOSE JWE/JWS/deriveKey/secret tests and adjusts core tests to new error messages and response shapes; changelog documents JOSE algorithm inference and entropy changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • aura-stack-ts/auth#125: Session strategy refactor introducing ctx.session and session lifecycle logic overlaps with session token and cookie handling modifications.
  • aura-stack-ts/auth#105: User-Agent header on OAuth userinfo requests relates to userinfo handling changes in packages/core/src/actions/callback/userinfo.ts.
  • aura-stack-ts/auth#91: Origin-matching adjustments are related to TrustedProxyHeadersConfig extraction from AuthConfig.

Suggested labels

security, breaking changes

Poem

🐰 I hopped through code and changed a type,
Moved trusted proxies out of sight,
Cookies now expire with care,
OAuth flows respond — not snare,
Keys infer algs, secrets get stern light!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective: hardening auth flows and jose operations through refactoring, which is reflected across multiple file changes including improved error handling, stricter validation, and security-focused updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/add-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/jose.ts (1)

279-287: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a rejection handler to the eagerly-started JOSE init promise

In packages/core/src/jose.ts (createJoseInstance()), const jose = (async () => { ... })() starts immediately, but there’s no attached rejection handler. If getSecrets()/key import fails before any method awaits jose, the failure can surface as an unhandled rejection during construction.

Suggested fix
     const jose = (async () => {
         const { jwsSecret, jweSecret, jwtSecret } = await getSecrets(secretKey, salt, session)

         return {
             jwt: createJWT<DefaultUser>(jwtSecret),
             jws: createJWS<DefaultUser>(jwsSecret),
             jwe: createJWE<DefaultUser>(jweSecret),
         }
     })()
+    void jose.catch(() => {})
 
     return {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/jose.ts` around lines 279 - 287, The eagerly-started
promise stored in the jose constant (created by the IIFE that calls getSecrets
and returns { jwt: createJWT(...), jws: createJWS(...), jwe: createJWE(...) })
has no rejection handler; attach a .catch handler to that promise to consume/log
the error and rethrow if appropriate (or otherwise surface it consistently) so
unhandled-rejection warnings are avoided — use the jose constant itself and the
getSecrets/createJWT/createJWS/createJWE symbols to locate the code and call
your project logger (or console.error) inside the .catch before rethrowing.
🧹 Nitpick comments (2)
packages/core/src/shared/errors.ts (2)

11-16: ⚡ Quick win

Simplify the type guard check.

The first check typeof errorConstructor === "function" is redundant since the parameter type ErrorConstructor already guarantees it's a constructor function. You can remove this check without losing safety.

♻️ Simplified implementation
 export const hasCaptureStackTrace = (errorConstructor: ErrorConstructor): errorConstructor is V8ErrorConstructor => {
-    return (
-        typeof errorConstructor === "function" &&
-        "captureStackTrace" in errorConstructor &&
-        typeof (errorConstructor as any).captureStackTrace === "function"
-    )
+    return "captureStackTrace" in errorConstructor && typeof (errorConstructor as any).captureStackTrace === "function"
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/shared/errors.ts` around lines 11 - 16, The type-guard
hasCaptureStackTrace contains a redundant typeof errorConstructor === "function"
check; update the function (hasCaptureStackTrace) to remove that first check and
only test that "captureStackTrace" is a property on errorConstructor and that
(errorConstructor as any).captureStackTrace is a function, keeping the return
type predicate : errorConstructor is V8ErrorConstructor unchanged so callers
still get the narrowed type.

3-17: ⚡ Quick win

Extract duplicated type guard to shared utility.

The V8ErrorConstructor interface and hasCaptureStackTrace function are duplicated in packages/jose/src/errors.ts. Consider extracting this to a shared utility module to maintain a single source of truth.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/shared/errors.ts` around lines 3 - 17, The
V8ErrorConstructor interface and hasCaptureStackTrace type guard are duplicated
(found in packages/core/src/shared/errors.ts and packages/jose/src/errors.ts);
extract them into a single shared utility module (e.g., export interface
V8ErrorConstructor and export const hasCaptureStackTrace from a new shared file)
and replace the duplicates by importing these symbols where needed (update
usages in functions/classes that reference V8ErrorConstructor and
hasCaptureStackTrace to import from the new shared module), then remove the
local duplicate definitions in both files so there is a single source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/`@types/config.ts:
- Around line 162-233: The union's discriminant prevents assigning a computed
boolean to trustedProxyHeaders; update the TrustedProxyHeadersConfig declaration
so trustedProxyHeaders is not a literal-only type (e.g., use
trustedProxyHeaders?: boolean instead of trustedProxyHeaders: true /
trustedProxyHeaders?: false) while keeping the trustedOrigins types
(TrustedOrigin[] | (request: Request) => Promise<TrustedOrigin[]> |
TrustedOrigin[] ) intact; this makes computed expressions like
process.env.AURA_AUTH_TRUSTED_PROXY_HEADERS === "true" type-check against
TrustedProxyHeadersConfig (symbol: TrustedProxyHeadersConfig, fields:
trustedProxyHeaders, trustedOrigins, types: TrustedOrigin, Request).

In `@packages/core/src/cookie.ts`:
- Around line 67-73: getExpiredCookie currently forces secure: true which can
prevent browsers from clearing non-secure cookies on HTTP; change the returned
object to preserve the incoming options' secure flag instead of forcing true
(i.e., set secure to options?.secure or leave it undefined when not provided)
while keeping expires: new Date(0) and maxAge: 0 so logout/cleanup works
correctly; update the function getExpiredCookie and its use of
SerializeOptions/options to reference the original secure value rather than
hardcoding true.

In `@packages/jose/CHANGELOG.md`:
- Line 19: The CHANGELOG line claiming "at least 3.5 bits of entropy per
character" is inconsistent with the implementation constant
MIN_SECRET_ENTROPY_PER_CHAR (value 4) in packages/jose/src/secret.ts; update the
CHANGELOG entry to state "at least 4 bits of entropy per character" (leave the
minimum total entropy 254 bits as-is) so the documentation matches the code.

In `@packages/jose/src/errors.ts`:
- Around line 28-30: The optional chaining after the type guard is redundant:
inside the constructor where hasCaptureStackTrace(Error) is checked, remove the
`?.` and call `Error.captureStackTrace(this, new.target)` directly; update the
block that currently reads `if (hasCaptureStackTrace(Error)) {
Error.captureStackTrace?.(this, new.target) }` to call
`Error.captureStackTrace(this, new.target)` so it matches the usage in the other
errors helper and relies on the type guard for safety.

---

Outside diff comments:
In `@packages/core/src/jose.ts`:
- Around line 279-287: The eagerly-started promise stored in the jose constant
(created by the IIFE that calls getSecrets and returns { jwt: createJWT(...),
jws: createJWS(...), jwe: createJWE(...) }) has no rejection handler; attach a
.catch handler to that promise to consume/log the error and rethrow if
appropriate (or otherwise surface it consistently) so unhandled-rejection
warnings are avoided — use the jose constant itself and the
getSecrets/createJWT/createJWS/createJWE symbols to locate the code and call
your project logger (or console.error) inside the .catch before rethrowing.

---

Nitpick comments:
In `@packages/core/src/shared/errors.ts`:
- Around line 11-16: The type-guard hasCaptureStackTrace contains a redundant
typeof errorConstructor === "function" check; update the function
(hasCaptureStackTrace) to remove that first check and only test that
"captureStackTrace" is a property on errorConstructor and that (errorConstructor
as any).captureStackTrace is a function, keeping the return type predicate :
errorConstructor is V8ErrorConstructor unchanged so callers still get the
narrowed type.
- Around line 3-17: The V8ErrorConstructor interface and hasCaptureStackTrace
type guard are duplicated (found in packages/core/src/shared/errors.ts and
packages/jose/src/errors.ts); extract them into a single shared utility module
(e.g., export interface V8ErrorConstructor and export const hasCaptureStackTrace
from a new shared file) and replace the duplicates by importing these symbols
where needed (update usages in functions/classes that reference
V8ErrorConstructor and hasCaptureStackTrace to import from the new shared
module), then remove the local duplicate definitions in both files so there is a
single source of truth.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 737d6758-dd1f-4338-a044-1b9e01a50e0f

📥 Commits

Reviewing files that changed from the base of the PR and between 6781c7c and 31dbde6.

📒 Files selected for processing (23)
  • packages/core/src/@types/config.ts
  • packages/core/src/actions/callback/access-token.ts
  • packages/core/src/actions/callback/callback.ts
  • packages/core/src/actions/callback/userinfo.ts
  • packages/core/src/actions/signIn/authorization.ts
  • packages/core/src/api/getSession.ts
  • packages/core/src/cookie.ts
  • packages/core/src/jose.ts
  • packages/core/src/session/cookie-manager.ts
  • packages/core/src/session/stateless.ts
  • packages/core/src/shared/crypto.ts
  • packages/core/src/shared/env.ts
  • packages/core/src/shared/errors.ts
  • packages/core/src/shared/utils.ts
  • packages/core/src/validator/registry.ts
  • packages/core/test/actions/callback/access-token.test.ts
  • packages/core/test/actions/callback/callback.test.ts
  • packages/core/test/actions/session/session.test.ts
  • packages/core/test/actions/signIn/authorization.test.ts
  • packages/jose/CHANGELOG.md
  • packages/jose/src/errors.ts
  • packages/jose/src/secret.ts
  • packages/jose/test/index.test.ts
💤 Files with no reviewable changes (3)
  • packages/core/test/actions/callback/callback.test.ts
  • packages/core/test/actions/session/session.test.ts
  • packages/core/src/session/stateless.ts

Comment thread packages/core/src/@types/config.ts
Comment thread packages/core/src/cookie.ts
Comment thread packages/jose/CHANGELOG.md Outdated
Comment thread packages/jose/src/errors.ts
Comment thread packages/core/test/actions/signIn/authorization.test.ts Dismissed
Comment thread packages/core/test/actions/signIn/authorization.test.ts Dismissed
Comment thread packages/core/test/actions/signIn/authorization.test.ts Dismissed
Comment thread packages/core/test/actions/signIn/authorization.test.ts Dismissed
Comment thread packages/core/test/actions/signIn/authorization.test.ts Dismissed
Comment thread packages/core/test/actions/signIn/authorization.test.ts Dismissed
Comment thread packages/core/test/actions/signIn/authorization.test.ts Dismissed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/jose/src/encrypt.ts`:
- Around line 43-47: The alg/key-management inference currently only checks
isCryptoKey(secret) and secret.type, which misses RSA JWK objects produced by
createSecret; update the logic in the EncryptJWT creation and corresponding
decrypt flows (where isAsymmetricCryptoKey is computed) to detect RSA JWKs by
inspecting secret.kty === "RSA" and treating JWK public keys as asymmetric
encryptable (set alg: "RSA-OAEP-256") and JWK private keys as asymmetric
decryptable, or alternatively convert JWKs to CryptoKey via createSecret before
deciding; implement the detection in the same places that compute
isAsymmetricCryptoKey (references: isCryptoKey, createSecret, EncryptJWT,
setProtectedHeader/alg) and add a regression test that encrypts with an RSA
public JWK (without passing alg) and decrypts with the matching RSA private JWK
(without specifying keyManagementAlgorithms) asserting successful round-trip.

In `@packages/jose/src/sign.ts`:
- Around line 33-38: signJWS/verifyJWS currently force "RS256" for any
asymmetric CryptoKey causing wrong alg selection for RSA-PSS, ECDSA, EdDSA keys;
change the logic that computes the JWS alg/algorithms (the isAsymmetricCryptoKey
branch around createSecret/isAsymmetricCryptoKey and the setProtectedHeader call
in SignJWT) to derive the proper JWS alg from the CryptoKey.algorithm.name (or
the key's algorithm metadata) instead of defaulting to "RS256": map "RSA-PSS" →
PS256/PS384/PS512 (choose matching hash if available), "RSASSA-PKCS1-v1_5" →
RS*, "ECDSA" → ES*, and "EdDSA" → EdDSA, and apply that computed alg to
setProtectedHeader (and for verifyJWS set allowed algorithms accordingly when
options.algorithms is not provided); keep honoring an explicit
options.alg/options.algorithms if caller supplied one.

In `@packages/jose/test/encrypt.test.ts`:
- Line 135: The test uses an invalid JWE enc value "RSA-OAP-256" in the
contentEncryptionAlgorithms option, causing early validation failures; update
the contentEncryptionAlgorithms array to use a valid JWE content encryption
algorithm (e.g. "A128GCM") so the negative test exercises the intended enc
mismatch with dir (the test that encrypts with enc: "A256GCM"); locate the
contentEncryptionAlgorithms setting in the encrypt.test.ts negative tests and
replace "RSA-OAP-256" with a valid enc like "A128GCM".

In `@packages/jose/test/sign.test.ts`:
- Around line 42-56: Tests are failing due to using short literal secrets
("my-secret-key", "wrong-secret-key") which trigger the built-in secret-length
validation instead of exercising token-parsing/signature paths; update both
tests to supply secrets >=32 bytes (e.g., use getRandomBytes(32) or derive a
32-byte secret via createDeriveKey/getRandomBytes and pass that into createJWS)
so verifyJWS is called with a valid-length secret and the expectations exercise
invalid-token and invalid-signature behavior for signJWS, verifyJWS, createJWS,
createDeriveKey and getRandomBytes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e2b4d967-b308-44a4-bf24-e272c1255544

📥 Commits

Reviewing files that changed from the base of the PR and between 31dbde6 and df07a97.

📒 Files selected for processing (15)
  • packages/core/src/@types/config.ts
  • packages/core/src/cookie.ts
  • packages/core/src/shared/errors.ts
  • packages/core/test/actions/signIn/authorization.test.ts
  • packages/jose/CHANGELOG.md
  • packages/jose/src/assert.ts
  • packages/jose/src/encrypt.ts
  • packages/jose/src/errors.ts
  • packages/jose/src/index.ts
  • packages/jose/src/sign.ts
  • packages/jose/test/deriveKey.test.ts
  • packages/jose/test/encrypt.test.ts
  • packages/jose/test/index.test.ts
  • packages/jose/test/secret.test.ts
  • packages/jose/test/sign.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/jose/CHANGELOG.md

Comment thread packages/jose/src/encrypt.ts Outdated
Comment thread packages/jose/src/sign.ts
Comment thread packages/jose/test/encrypt.test.ts Outdated
Comment thread packages/jose/test/sign.test.ts
@halvaradop halvaradop merged commit 9badef4 into master May 22, 2026
7 checks passed
@halvaradop halvaradop deleted the refactor/add-hardening branch May 22, 2026 21:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor Refactor without changing behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants