Previous attempt (commit 55cc75e7d) tried to fix the broken JWT mint
in /api/v1/auth/login by switching the cookie name from
`mana.session_token` to `__Secure-mana.session_token` for production.
That was necessary but not sufficient: Better Auth's session cookie
value isn't just the raw session token, it's `<token>.<HMAC>` where
the HMAC is derived from the better-auth secret. Reconstructing the
cookie from auth.api.signInEmail's JSON response only gave us the raw
token, so /api/auth/token's get-session middleware still couldn't
validate it and the JWT mint kept silently failing.
Real fix: do the sign-in via auth.handler (the HTTP path) rather than
auth.api.signInEmail (the SDK path). The handler returns a real fetch
Response with a Set-Cookie header containing the fully signed cookie
envelope. We capture that header verbatim and forward it as the cookie
on the /api/auth/token request, which now passes validation and mints
the JWT correctly.
Verified end-to-end on auth.mana.how:
$ curl -X POST https://auth.mana.how/api/v1/auth/login \
-d '{"email":"...","password":"..."}'
{
"user": {...},
"token": "<session token>",
"accessToken": "eyJhbGciOiJFZERTQSI...", ← real JWT now
"refreshToken": "<session token>"
}
Side benefits:
- The email-not-verified path is now handled by checking
signInResponse.status === 403 directly, no more catching APIError
with the comment-noted async-stream footgun.
- X-Forwarded-For is forwarded explicitly so Better Auth's rate limiter
and our security log see the real client IP.
- The leftover catch block now only handles unexpected exceptions
(network errors etc); the FORBIDDEN-checking logic in it is dead but
harmless and left in for defense in depth.
Only include emailOptions object when from or replyTo is provided,
preventing validation errors when these optional fields are not set.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>