Skip to content

feat(auth): add login with JWT access and refresh tokens#282

Merged
onerandomdevv merged 2 commits into
devfrom
feat/auth-login-jwt
May 21, 2026
Merged

feat(auth): add login with JWT access and refresh tokens#282
onerandomdevv merged 2 commits into
devfrom
feat/auth-login-jwt

Conversation

@onerandomdevv

@onerandomdevv onerandomdevv commented May 21, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

This PR implements B-05: Auth Login + JWT + Sessions.

It adds authenticated session handling for Twizrr users after registration. Login now validates user credentials, creates a session record, stores JWT access and refresh tokens in HttpOnly cookies, and returns frontend redirect information based on whether the user owns a store.

Added endpoints:

  • POST /auth/login
  • POST /auth/logout
  • POST /auth/refresh
  • POST /auth/forgot-password
  • POST /auth/reset-password

Session handling:

  • Login creates a Session row.
  • Refresh token is hashed with SHA-256 before storage.
  • Refresh validates the cookie refresh token against an active, unexpired, unrevoked session.
  • Refresh rotates the refresh token and updates the session hash.
  • Logout revokes the matching session by setting revokedAt.
  • Password reset revokes all active sessions for the user.

Cookie names:

  • twizrr_access_token
  • twizrr_refresh_token

Type of change

  • New feature
  • Bug fix
  • Refactor / cleanup
  • Database migration included
  • Chore / maintenance
  • Documentation

How to test this

  1. Checkout the branch:

    git checkout feat/auth-login-jwt
    
  2. Run Backend test:
    cd apps/backend
    pnpm run lint
    npx tsc --noEmit
    pnpm run build

  3. Start the backend and test auth endpoints:
    POST /auth/login
    POST /auth/refresh
    POST /auth/logout
    POST /auth/forgot-password
    POST /auth/reset-password

  4. Expected result:
    Login returns auth cookies, not tokens in the JSON response body.
    Login response includes redirectTo.
    Refresh works only with a valid, active, unexpired session.
    Logout revokes the active session and clears cookies.
    Forgot-password does not reveal whether an email exists.
    Reset-password updates the password and revokes active se

Area affected

  • Backend
  • Web
  • WhatsApp
  • Shared package
  • Database / Prisma
  • GitHub / CI / infrastructure

Pre-commit checklist

  • Backend lint/type/build pass when backend is affected
  • Web lint/type/build pass when web is affected
  • Shared package build passes when shared is affected
  • No console.log left in production code
  • No secrets or .env files committed
  • No new any types added
  • No non-MVP legacy features reintroduced
  • All money values are BigInt kobo, never float
  • Paystack webhook changes verify HMAC before processing
  • Database migrations are Prisma migrations, not db push
  • Database migrations are backward-compatible or risk is documented

Screenshots

Notes for reviewer

@codesandbox

codesandbox Bot commented May 21, 2026

Copy link
Copy Markdown

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@onerandomdevv has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 46 minutes and 44 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: edc87ec1-7bac-4ec8-a7fa-85f48df68493

📥 Commits

Reviewing files that changed from the base of the PR and between 4f068e6 and 7ecd6ec.

📒 Files selected for processing (4)
  • apps/backend/src/domains/users/auth/auth.controller.ts
  • apps/backend/src/domains/users/auth/auth.types.ts
  • apps/backend/src/domains/users/auth/strategies/jwt-refresh.strategy.ts
  • apps/backend/src/domains/users/auth/strategies/jwt.strategy.ts
📝 Walkthrough

Walkthrough

This PR implements a complete JWT-based authentication system with HTTP-only cookie storage, session management, and password reset flows. It adds type contracts, validation DTOs, two Passport strategies for access/refresh tokens, core auth service methods with token signing and session persistence, module wiring, and controller integration.

Changes

JWT Authentication with Cookie Sessions

Layer / File(s) Summary
Auth Types and Constants
apps/backend/src/domains/users/auth/auth.types.ts, apps/backend/src/domains/users/auth/auth.constants.ts
Defines JWT access/refresh payload structures, authenticated user shapes with verification flags, session metadata, and cookie/TTL constants with as const literal typing.
Input Validation DTOs
apps/backend/src/domains/users/auth/dto/login.dto.ts, apps/backend/src/domains/users/auth/dto/password-reset.dto.ts
Implements LoginDto with email normalization and validation, and ForgotPasswordDto/ResetPasswordDto for password reset flows with strength requirements.
JWT Access Token Strategy
apps/backend/src/domains/users/auth/strategies/jwt.strategy.ts
Cookie-based JWT extractor and Passport strategy that validates access tokens, looks up active users by subject, and returns AuthenticatedRequestUser with role and store context.
JWT Refresh Token Strategy
apps/backend/src/domains/users/auth/strategies/jwt-refresh.strategy.ts
Passport strategy that extracts and hashes refresh tokens from cookies, queries Prisma for active unexpired sessions, and returns RefreshRequestUser with session id and user details.
Auth Service Methods
apps/backend/src/domains/users/auth/auth.service.ts
Implements login, refresh, logout, forgotPassword, and resetPassword with JWT signing, session hash storage, SHA-256 token hashing, password verification, and email delivery via ResendClient.
Auth Module JWT Configuration
apps/backend/src/domains/users/auth/auth.module.ts
Configures async JWT module with separate access/refresh secrets and TTLs from ConfigService, registers JWT strategies and services.
Auth Controller Cookie Integration
apps/backend/src/domains/users/auth/auth.controller.ts
Adds private helpers for setting/clearing HTTP-only cookies with secure/sameSite settings and extracting session metadata, updates login/logout/refresh handlers to use them.
Module Reorganization
apps/backend/src/modules/auth/auth.module.ts, apps/backend/src/domains/users/auth/strategies/local.strategy.ts
Removes JWT strategies from main AuthModule providers (already registered in UsersAuthModule), updates LocalStrategy error to direct users to POST /auth/login.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AuthController
  participant JwtAccessStrategy
  participant JwtRefreshStrategy
  participant AuthService
  participant Prisma
  Client->>AuthController: POST /auth/login { email, password }
  AuthController->>AuthService: login(dto, sessionMetadata)
  AuthService->>Prisma: user.findFirst by email
  AuthService->>AuthService: verify password
  AuthService->>AuthService: signTokenPair()
  AuthService->>Prisma: createSession with refresh hash
  AuthService-->>AuthController: { accessToken, refreshToken, redirectTo }
  AuthController->>AuthController: setAuthCookies(tokens)
  AuthController-->>Client: Set-Cookie: accessToken, refreshToken
  Client->>AuthController: GET /auth/refresh
  AuthController->>JwtRefreshStrategy: guard validates refresh cookie
  JwtRefreshStrategy->>Prisma: findFirst session by hash
  JwtRefreshStrategy-->>AuthController: RefreshRequestUser
  AuthController->>AuthService: refresh(refreshUser, sessionMetadata)
  AuthService->>AuthService: signTokenPair()
  AuthService->>Prisma: update session hash/expiry
  AuthService-->>AuthController: { accessToken, refreshToken }
  AuthController->>AuthController: setAuthCookies(tokens)
  AuthController-->>Client: Set-Cookie: accessToken, refreshToken
  Client->>AuthController: protected route
  AuthController->>JwtAccessStrategy: guard validates access cookie
  JwtAccessStrategy->>Prisma: user.findFirst by sub
  JwtAccessStrategy-->>AuthController: AuthenticatedRequestUser
  AuthController-->>Client: protected resource
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • coded-devs/twizrr#48: Implements HTTP-only cookie-based auth/refresh flow by updating JWT cookie extraction and auth controller cookie set/clear logic alongside middleware/strategy wiring.

Poem

🐰 Whisker-twitching with delight,
JWTs now baked just right,
Cookies crumbly, sessions stay,
Refresh tokens save the day!
Auth flows bloom in morning light. 🌸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning PR description covers objectives, implementation details, testing steps, but has incomplete sections and formatting issues. Complete the incomplete sections: finish the 'Expected result' in testing steps, add pre-commit checklist items (secrets, any types, legacy features, migrations), and provide reviewer notes if applicable.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(auth): add login with JWT access and refresh tokens' accurately and clearly summarizes the main feature added in this PR: login functionality with JWT tokens.
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 feat/auth-login-jwt

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
apps/backend/src/domains/users/auth/strategies/jwt-refresh.strategy.ts (1)

16-18: 💤 Low value

Consider extracting shared type to a common location.

RequestWithCookies is duplicated between jwt.strategy.ts and jwt-refresh.strategy.ts. Could be extracted to auth.types.ts for DRY compliance.

🤖 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 `@apps/backend/src/domains/users/auth/strategies/jwt-refresh.strategy.ts`
around lines 16 - 18, RequestWithCookies is duplicated in jwt.strategy and
jwt-refresh.strategy; extract it into a shared auth.types module and import it
from both strategies. Create an auth.types.ts that exports the
RequestWithCookies type, remove the local duplicate declarations from
jwt.strategy.ts and jwt-refresh.strategy.ts, and update their imports to use the
new auth.types export so both strategies reference the single shared type.
🤖 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 `@apps/backend/src/domains/users/auth/auth.controller.ts`:
- Around line 115-126: The logout endpoint currently returns a plain object from
authService.logout; wrap the response into the project's envelope shape before
returning. In the logout method (auth.controller.logout) call const result =
await this.authService.logout(refreshToken) as needed, then return an envelope
such as { success: true, data: result } (or { success: true, data: { loggedOut:
true } } if you want to be explicit); keep the existing
clearAuthCookies(response) call and HTTP status. Ensure any error paths also
return the envelope shape (success: false with message/code) if applicable.
- Around line 128-145: The refresh controller currently returns a raw object;
change the response to the required envelope format. In the refresh method
(auth.controller.refresh) after setAuthCookies(response, tokens) return an
envelope like { success: true, data: { refreshed: true } } (or include any
message/code as needed) instead of { refreshed: true }; ensure no other callers
rely on the old shape and keep authService.refresh and getSessionMetadata usage
unchanged.
- Around line 97-113: The login endpoint currently returns a raw object; change
it to return the required envelope shape. In the login method, after calling
this.authService.login(...) and this.setAuthCookies(...), return an envelope
object like { success: true, data: { redirectTo: result.redirectTo } }
(optionally include message/code if relevant) so the login controller (method:
login) conforms to the app-wide response format; keep the calls to
this.getSessionMetadata(request) and this.setAuthCookies(response, result)
unchanged.
- Around line 153-165: Update the controller methods forgotPassword and
resetPassword to return responses in the required envelope shape instead of
returning service results directly: call this.authService.forgotPassword(dto)
and this.authService.resetPassword(dto) as before, but wrap their successful
output into { success: true, data: <serviceResult>, message?: <optionalMessage>,
code?: <optionalCode> } (or use a shared helper like createEnvelope(result,
message) if available) so both endpoints always emit { success, data, message,
code } envelope objects; keep error handling unchanged (errors should still
propagate to the global exception filter which can emit { success: false,
message, code }).

In `@apps/backend/src/domains/users/auth/auth.types.ts`:
- Around line 21-22: Rename the boolean fields emailVerified and phoneVerified
to isEmailVerified and isPhoneVerified in the auth types (e.g., the
interface/type where emailVerified and phoneVerified are defined) to meet the
boolean-naming convention; update all references/usages (serializers, DB
mappings, DTOs, tests, and any code that reads/writes these properties) to the
new names and adjust any type imports/exports that reference the old symbols so
compilation and runtime mapping remain consistent.

---

Nitpick comments:
In `@apps/backend/src/domains/users/auth/strategies/jwt-refresh.strategy.ts`:
- Around line 16-18: RequestWithCookies is duplicated in jwt.strategy and
jwt-refresh.strategy; extract it into a shared auth.types module and import it
from both strategies. Create an auth.types.ts that exports the
RequestWithCookies type, remove the local duplicate declarations from
jwt.strategy.ts and jwt-refresh.strategy.ts, and update their imports to use the
new auth.types export so both strategies reference the single shared type.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0d18028f-628e-4880-890e-b8ba32a030ed

📥 Commits

Reviewing files that changed from the base of the PR and between 55ea487 and 4f068e6.

📒 Files selected for processing (11)
  • apps/backend/src/domains/users/auth/auth.constants.ts
  • apps/backend/src/domains/users/auth/auth.controller.ts
  • apps/backend/src/domains/users/auth/auth.module.ts
  • apps/backend/src/domains/users/auth/auth.service.ts
  • apps/backend/src/domains/users/auth/auth.types.ts
  • apps/backend/src/domains/users/auth/dto/login.dto.ts
  • apps/backend/src/domains/users/auth/dto/password-reset.dto.ts
  • apps/backend/src/domains/users/auth/strategies/jwt-refresh.strategy.ts
  • apps/backend/src/domains/users/auth/strategies/jwt.strategy.ts
  • apps/backend/src/domains/users/auth/strategies/local.strategy.ts
  • apps/backend/src/modules/auth/auth.module.ts

Comment thread apps/backend/src/domains/users/auth/auth.controller.ts
Comment thread apps/backend/src/domains/users/auth/auth.controller.ts
Comment thread apps/backend/src/domains/users/auth/auth.controller.ts
Comment thread apps/backend/src/domains/users/auth/auth.controller.ts
Comment thread apps/backend/src/domains/users/auth/auth.types.ts Outdated
@onerandomdevv onerandomdevv merged commit d9b2519 into dev May 21, 2026
8 checks passed
@onerandomdevv onerandomdevv deleted the feat/auth-login-jwt branch May 21, 2026 03:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant