Extract an AuthProvider interface behind Wasp's session layer - #4714
Closed
FranjoMindek wants to merge 4 commits into
Closed
Extract an AuthProvider interface behind Wasp's session layer#4714FranjoMindek wants to merge 4 commits into
FranjoMindek wants to merge 4 commits into
Conversation
FranjoMindek
temporarily deployed
to
fly-deploy-test
August 13, 2026 19:40 — with
GitHub Actions
Inactive
FranjoMindek
had a problem deploying
to
railway-deploy-test
August 13, 2026 19:40 — with
GitHub Actions
Failure
15 tasks
@wasp.sh/spec
@wasp.sh/wasp-cli
@wasp.sh/wasp-cli-darwin-arm64-unknown
@wasp.sh/wasp-cli-darwin-x64-unknown
@wasp.sh/wasp-cli-linux-arm64-glibc
@wasp.sh/wasp-cli-linux-x64-glibc
@wasp.sh/wasp-cli-linux-x64-musl
commit: |
FranjoMindek
had a problem deploying
to
railway-deploy-test
August 13, 2026 20:12 — with
GitHub Actions
Failure
FranjoMindek
temporarily deployed
to
fly-deploy-test
August 13, 2026 20:12 — with
GitHub Actions
Inactive
FranjoMindek
force-pushed
the
franjo/auth-provider-interface
branch
from
August 13, 2026 20:40
5446b47 to
946d141
Compare
FranjoMindek
temporarily deployed
to
fly-deploy-test
August 13, 2026 20:48 — with
GitHub Actions
Inactive
FranjoMindek
had a problem deploying
to
railway-deploy-test
August 13, 2026 20:48 — with
GitHub Actions
Failure
FranjoMindek
had a problem deploying
to
railway-deploy-test
August 13, 2026 21:19 — with
GitHub Actions
Failure
15 tasks
Contributor
Author
|
Superseded by the restacked series (stack of #4724–#4730). The AuthProvider extraction now lives in #4725, where the contract is born directly in @wasp.sh/auth-contract with its final authenticate() shape, so the intermediate verifyRequest/verifyCredential design from this PR no longer exists anywhere in the history. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Stack: PR 1 of a series exploring a pluggable auth provider interface for Wasp. This one is a pure internal refactor with no user-visible change.
Wasp's session layer imports Lucia directly and leaks Lucia's
Sessiontype out ofsession.ts. This PR puts an explicitAuthProviderinterface in between, implemented by today's Lucia-backed code, so that the request middleware, the websocket handler, the logout route and the login paths depend on an interface rather than on a session library.Lucia is now confined to exactly two files (
lucia.tsandprovider/wasp.ts). Nothing else references it.This is valuable on its own — it is essentially what #3081 and #4677 ask for — and it is a prerequisite for making the provider selectable later.
Why this shape
I designed this four different ways (minimalist / capability-tiers / inverted-control / migration-first) and had them judged against concrete criteria. The decisions that survived:
Verify, not fetch. The primitive is
verifyRequest(req) -> VerifiedSession | null, notfindById(id). AdonisJS shapes its provider contract aroundfindById, which assumes identity is retrievable by an opaque id — exactly the assumption an external provider breaks, since it validates a signed credential and cannot look a subject up on our behalf. RejectedfindByIdfor that reason.Base interface + capability extension, not one interface with optional members. RedwoodJS modelled every optional capability as a separate generic and ended up with 13 type parameters on its client interface.
SessionIssuingAuthProvider extends AuthProviderkeeps the base readable. There are exactly two tiers today, so two interfaces is the right size; if it grows past ~3 the better encoding is a singleAuthProvider<C extends Capability>with the optional half derived byPick.The subject stays opaque.
VerifiedSession.subjectIdis provider-owned and Wasp resolves it. Wasp's sessions are keyed onAuth.id, not on the developer'sUser.id— a port designed aroundissueSession(userId)would force the wasp-auth adapter to do theAuth -> Userjoin itself (breaking "the provider never touches the DB") or force core into an extra lookup per request. This mismatch would not bite any hosted provider, only the one adapter the whole migration is built around.Rejected: a single
authenticate(Request)entry point. It is cleaner than having bothverifyRequestandverifyCredential, and it is where this should end up — Clerk and WorkOS both take aRequest. But websockets carry a bare token insocket.handshake.auth.sessionId, so collapsing the two means a websocket wire change. That belongs in its own PR with a back-compat window, not here.One subtlety worth reviewing
The old code got
Auth.userIdfor free out of Lucia'svalidateSession(viagetUserAttributes) and then loaded the user by that id. Resolving through the provider interface loses that free ride, so a naive port would add a DB round trip per authenticated request. InsteadtoSessionAndUserloads the user through the auth relation in a single query:Same query count as before, and it is the shape every future provider needs anyway.
Known signature change
wasp/server/auth/sessionis a public entrypoint, andgetSessionAndUserFrom*now returns{ sessionId, user }rather than{ session, user }. Everything in that module is markedPRIVATE APIand only.idwas ever read from the oldsessionobject, but it is a change to an exported signature.Not in this PR
Provider selection, JIT provisioning, capability-gated codegen, and any second adapter.
provider/index.tsexports a single hardcodedauthProvider— that is the seam those later PRs widen.The full design, the uniform/tiered/provider-specific classification of Wasp's public auth API, and the rest of the stack are in
waspc/docs/auth-provider-interface-plan.md, added here.Type of change
Checklist
I tested my change in a Wasp app to verify that it works as intended.
Verified live against a running app — signup 200, login returns
sessionId,/auth/me200 with the fullAuthUserand identities intact, wrong password 401, logout 200,/auth/me401 after logout, no-token{"json":null}. Identical to pre-refactor behaviour.🧪 Tests and apps:
I added unit tests for my change.
No new unit tests: this is a behaviour-preserving refactor and the existing e2e golden snapshots are the stronger check — they compare generated output verbatim. All 932 e2e tests pass.
(if you fixed a bug) I added a regression test for the bug I fixed.
(if you added/updated a feature) I added/updated e2e tests in
examples/kitchen-sink/e2e-tests.(if you added/updated a feature) I updated the starter templates in
waspc/data/Cli/templates, as needed.(if you added/updated a feature) I updated the example apps in
examples/, as needed.examples/tutorials) I updated the tutorial in the docs (and vice versa).📜 Documentation:
(if you added/updated a feature) I added/updated the documentation in
web/docs/.No user-facing change to document. Design rationale is in
waspc/docs/.🆕 Changelog: (if change is more than just code/docs improvement)
waspc/ChangeLog.mdwith a user-friendly description of the change.web/docs/migration-guides/.versioninwaspc/waspc.cabalto reflect the changes I introduced.Note on the goldens
I excluded one file the regeneration touched:
wasp-build-golden/.../web-app/build/assets/200.js. It is a bundler formatting change (braces added around anif/else) in client output this PR does not touch, and it reproduced through a cleanaccept-all, so it looks like local toolchain drift rather than anything here. Flagging it in case it is real.