Auth architecture: where DaloyJS fits in OAuth2 & OpenID Connect
This is the page to read before you wire up login. DaloyJS (like Hono, Express, Fastify, or ASP.NET Core) is a web framework. It is excellent at verifying and enforcing identity on each request, but it deliberately does not ship a login UI, a user database, or an OAuth2 authorization server. Those belong to an identity provider (IdP) that you bring to the table.
The short answer
- DaloyJS is a resource server (and a toolkit for building a relying party). It checks tokens; it does not issue them.
- It is not an "IdentityServer". It cannot, on its own, do what Duende IdentityServer, Keycloak, or Auth0 do: run login pages, manage clients and consent, and mint tokens.
- You do need an OpenID Connect provider, but it does not have to be Auth0, Okta, or Clerk specifically. It can be any standards-compliant IdP, including self-hosted open-source ones.
- Do not build your own authorization server. Verify tokens from a vetted provider instead.
The three OAuth2 / OpenID Connect roles
Every OAuth2 / OIDC deployment splits responsibilities across three roles. Confusion about "can DaloyJS do OAuth2?" almost always comes from collapsing these into one box.
| Responsibility | OAuth2 / OIDC role | Who plays it | DaloyJS? |
|---|---|---|---|
| Owns login, consent, and clients; mints & refreshes tokens | Authorization Server / OpenID Provider (OP) | Auth0, Okta, Entra ID, Cognito, Keycloak, Zitadel, Ory… | No |
| Accepts a token, verifies it, returns protected data | Resource Server | Your API | Yes |
| Starts the login flow and holds the user's session | Client / Relying Party (RP) | Your SPA, mobile app, or a server-side BFF | Yes (building blocks) |
Where DaloyJS fits (and where it doesn't)
DaloyJS owns the Resource Server role outright, and it gives you everything you need to build the Client / Relying Party (the back-end-for-frontend, or BFF). What it does not do is play the Authorization Server: it will not render a login page, store passwords, run a consent screen, expose a /.well-known/openid-configuration discovery document, or issue access and refresh tokens to third-party clients. That is the IdP's job, and reimplementing it is exactly the kind of security-critical work you should not take on yourself.
DaloyJS vs .NET, ASP.NET Core, and Duende IdentityServer
A common question is "what is the difference between DaloyJS and .NET?" They are not the same kind of thing. .NET is a whole platform: a runtime (the CLR), a large standard library, and an ecosystem of first-party frameworks. DaloyJS is a single web framework that runs on JavaScript runtimes. The closest .NET analog to DaloyJS is ASP.NET Core, not ".NET" as a whole. And the identity pieces that ship in the .NET ecosystem (Duende IdentityServer, OpenIddict, ASP.NET Core Identity) have no built-in DaloyJS equivalent on purpose, you bring an external IdP.
| Layer | .NET world | JavaScript / DaloyJS world |
|---|---|---|
| Language & runtime | C# / F# on the CLR | TypeScript on Node, Bun, Deno, Workers, Vercel Edge |
| Standard library | .NET base class library (BCL) | The runtime's web-platform APIs (fetch, Web Crypto…) |
| Web framework | ASP.NET Core (Minimal APIs, MVC) | DaloyJS (or Hono, Express, Fastify, Elysia) |
| Token validation (resource server) | Microsoft.AspNetCore.Authentication.JwtBearer | jwk() / createJwtVerifier() / bearerAuth() |
| Authorization server / login (issues tokens) | Duende IdentityServer, OpenIddict, ASP.NET Core Identity | A separate IdP (managed or self-hosted) — no built-in equivalent |
So yes: DaloyJS and Hono are frameworks, and on their own they cannot do what Duende IdentityServer does. IdentityServer is an authorization server. DaloyJS sits in front of your business logic and trusts the tokens an authorization server issues.
Do you need Auth0, Okta, or Clerk?
You need an OpenID Connect provider. You do not need those three brands specifically. Any provider that exposes a standard JWKS endpoint and OIDC discovery works with the same one line of DaloyJS code. Pick the operational model that fits your team:
Managed (fastest to ship)
Someone else runs the IdP; you configure it. Good default for most teams.
- Auth0, Okta, Clerk, Microsoft Entra ID, AWS Cognito
- Google Identity, Stytch, WorkOS, Firebase Authentication, and others
Self-hosted open source (full control)
You operate the IdP yourself. Choose this for data residency, air-gapped environments, or cost control at scale.
Whatever you pick, treat building your own authorization server as a non-goal. Implementing OAuth2 / OIDC correctly — authorization-code + PKCE, token rotation, key management and rotation, consent, discovery, and the long tail of spec edge cases — is a large, high-risk surface that vetted IdPs already solve.
Recommended architecture 1: API as a resource server
This is the default and the most common shape. Your API trusts tokens issued by your IdP, verifies them on every request against the provider's JWKS, and authorizes per route by scope. It works identically across every runtime DaloyJS targets, including the edge.
The jwk() middleware enforces an asymmetric-only algorithm allowlist (it refuses HS* to block the classic confused-deputy attack), checks issuer and audience, caches the JWKS, and sends Cache-Control: no-store on its 401 challenges. See the auth slice for the full behavior, and the per-provider guides for Auth0, Okta, Entra ID, Cognito, and Clerk.
Recommended architecture 2: browser app (the BFF pattern)
If a browser app needs users to log in, do not hold access or refresh tokens in JavaScript. Run a thin server-side back-end-for-frontend (BFF): it performs the authorization-code + PKCE flow with the IdP, keeps the resulting tokens in a signed, encrypted session() cookie, and exposes only same-origin endpoints to the browser. Protect every state-changing route with csrf() because the browser now authenticates with a cookie.
The login/callback routes themselves drive the OIDC flow against your provider. See sessions and CSRF for the building blocks.
First-party building blocks
| Helper | Import | Role it serves |
|---|---|---|
jwk() | @daloyjs/core/jwk | Verify asymmetric JWTs against a JWKS (resource server) |
createJwtVerifier() | @daloyjs/core | Lower-level JWT verification when you manage the keys |
bearerAuth() | @daloyjs/core | Validate opaque or custom Bearer tokens with your own hook |
requireScopes() | @daloyjs/core | Authorize a route by scope or permission |
basicAuth() | @daloyjs/core | HTTP Basic for simple internal cases |
session() | @daloyjs/core | Signed, encrypted cookie session for a BFF / relying party |
csrf() | @daloyjs/core | CSRF protection for cookie-authenticated, state-changing routes |
createJwtSigner() | @daloyjs/core | Mint your own JWTs for service-to-service or a tiny first-party issuer |
What we recommend
- Default to a resource server. Verify JWTs with
jwk(), pin an asymmetric algorithm allowlist, enforceissuer+audience, and gate routes withrequireScopes(). - Use the BFF pattern for browser logins. Run authorization-code + PKCE on the server, keep tokens in a
session()cookie, never expose them to JavaScript, and protect mutations withcsrf(). - Bring an IdP you do not operate unless you have a strong reason not to: managed for speed, self-hosted open source for control and data residency.
- Never build your own authorization server.
- Service-to-service / internal traffic: use
bearerAuth()with a verified token, orcreateJwtSigner()+jwk()when both sides speak JWT. See the internal-service preset.