DEV Community

Herve Tribouilloy
Herve Tribouilloy

Posted on

Implementing Login in a Next.js App with Passport, JWT, and DDD

Introduction

I originally built a Next.js application that required a login feature.
From the start, I wanted this login to support OAuth to improve user convenience and avoid reinventing authentication flows.

To do this properly, I chose Passport (a well-established authentication library) and used JWTs as the security artefact for authenticated identity.

So far, this is fairly standard.

A deliberate DDD split

Where this became interesting is that I wanted the application to follow Domain-Driven Design (DDD) principles.

Instead of letting authentication live “wherever it was convenient”, I explicitly split the system into three components:

  1. Backend Responsible for data persistence and business rules (API / GraphQL, user model, permissions, session validity)
  2. OAuth Express server A custom Express server that owns Passport entirely (strategies, login orchestration, token issuance)
  3. Frontend A Next.js application responsible only for UI and user interaction

OAuth Express server
A custom Express server that owns Passport entirely
(strategies, login orchestration, token issuance)

This separation had a very clear goal:

Authentication should be reusable independently of the frontend technology.

With this setup:

  • The frontend could change
  • The backend could change
  • OAuth could be reused across projects
  • without rewriting the login logic or coupling Passport to a specific UI framework.

So far, this architecture worked well.

The subtle convenience of Next.js

Next.js, however, has a unique property:

It runs both on the server and in the browser.

This creates a powerful shortcut.

In this architecture:

  • Next.js can call the OAuth server server-side
  • Receive the JWT outside the browser
  • Then forward that JWT to the client and store it in a cookie

From a developer perspective, this feels elegant and simple.

But architecturally, something subtle is happening:

The frontend is now participating in authentication storage.

Even though Passport lives elsewhere, Next.js becomes part of the trust boundary, because it:

  • receives the JWT server-side
  • decides how it is persisted in the browser

Conclusion

Next.js is a powerful environment.

Its ability to run code both server-side and client-side makes it well suited for implementing authentication flows. In this architecture, Next.js can safely receive a JWT on the server and persist it in the browser as a cookie within a single execution path.

By applying Domain-Driven Design, authentication logic lives outside the frontend instead of being buried inside a single framework. This keeps responsibilities clear and boundaries explicit, while still allowing the frontend to take advantage of Next.js’s capabilities.

The important takeaway is that the login feature is only part of the result. By separating concerns, two out of three components can now be reused unchanged in another project — something I’ll explore in a follow-up article.

If you’d like to explore this architecture in practice, the OAuth component described in this article is available as a public repository:

👉 https://github.com/digitalrisedorset/oauth-signin

The repository contains the standalone OAuth Express server that owns the Passport implementation and JWT issuance, exactly as described above. It’s designed to be reused across projects, independently of the frontend framework.

Top comments (0)