next-auth
next-auth copied to clipboard
feat(frameworks): add @auth/fastify
โ๏ธ Reasoning
This PR is a port of @rexfordessilfie's @auth/express to Fastify. It uses similar translation layer with Web API Request and Response to fastify types. I opted to make it a plugin.
Implementation
- Introduce a
toWebRequesthelper that converts anFastifyRequestinto a web - Introduce a
toFastifyReplyhelper that converts a webRequestinto anFastifyReply - Introduce an
FastifyAuth(authConfig)initializer which returns a fastify plugin function of typeFastifyPluginAsyncto fulfill authentication requests. Under the hood, it: - It calls
toWebRequest(request)to get a webRequest - Forwards the web request to
Auth(from@auth/core) to get back a webResponse - Forwards the web response along with Fastify's
replytotoFastifyReply(response, reply)to respond to fulfill the request
Tests
- Tests the
toWebRequestandtoFastifyReplyhelpers to ensure that they forward all headers, and body and in the right format (depending on content-type) - Tests the full login flow of a credentials provider
- Tests the
getSessionby mocking the session
Documentation
~I still haven't finished converting the express docs to fastify, just the main example is done so far.~
Notes
-
In the docs, I've added the trust proxy for the https issue. I've yet to test if this is actually required in fastify.
-
For the async handlers, I've used
return bodyinstead ofreply.send(body). I'm not sure whats preferred. -
The body is unknown type in
FastifyRequestso I checkedtypeof req.body === 'object' && req.body !== nullinencodeRequestBody. I'm not sure if this is ok. In order to testencodeUrlEncoded, I had to add checks for the body type. -
For the response tests, the fastify injector returned the body as a string, so I had to stringify the expected value in the test equality. It also added
; charset=utf-8to the end of the content type header, so I stripped that off for the test equality. I'm not sure if this is an issue. -
@fastify/formbodymight need to be a peer dependency, I'm not sure.
๐งข Checklist
- [x] Documentation
- [x] Tests
- [x] Ready to be merged
๐ซ Affected issues
๐ Resources
The latest updates on your projects. Learn more about Vercel for Git โ๏ธ
| Name | Status | Preview | Comments | Updated (UTC) |
|---|---|---|---|---|
| auth-docs | โ Ready (Inspect) | Visit Preview | ๐ฌ Add feedback | Oct 1, 2024 6:24am |
1 Skipped Deployment
| Name | Status | Preview | Comments | Updated (UTC) |
|---|---|---|---|---|
| next-auth-docs | โฌ๏ธ Ignored (Inspect) | Visit Preview | Oct 1, 2024 6:24am |
@hillac is attempting to deploy a commit to the authjs Team on Vercel.
A member of the Team first needs to authorize it.
@ianschmitz How does this compare to the method you used?
@ianschmitz How does this compare to the method you used?
I didn't get a chance to take it all the way to production, so I don't think I have a ton of feedback to give
So first of all, thanks for contributing this! I'm working on a fastify application as we speak and had been putting off integrating Auth.js :joy:
Anyway, I did some testing and it seems it's having an issue with esm/cjs and @auth/core. Error message printed when starting:
{"level":50,"time":1705315403580,"pid":1221767,"hostname":"ndo4","err":
{"type":"Error","message":"No \"exports\" main defined in /opt/ndomino/sveltekasten-
rss/node_modules/@auth/core/package.json","stack":"Error [ERR_PACKAGE_PATH_NOT_EXPORTED]:
No \"exports\" main defined in /opt/ndomino/sveltekasten-
rss/node_modules/@auth/core/package.json\n at __node_internal_captureLargerStackTrace
(node:internal/errors:497:5)\n at new NodeError (node:internal/errors:406:5)\n at
exportsNotFound (node:internal/modules/esm/resolve:268:10)\n at packageExportsResolve
(node:internal/modules/esm/resolve:542:13)\n at resolveExports
(node:internal/modules/cjs/loader:547:36)\n at Module._findPath
(node:internal/modules/cjs/loader:621:31)\n at Module._resolveFilename
(node:internal/modules/cjs/loader:1034:27)\n at a._resolveFilename
(/opt/ndomino/sveltekasten-
rss/node_modules/.pnpm/[email protected]/node_modules/tsx/dist/cjs/index.cjs:1:1729)\n at
Module._load (node:internal/modules/cjs/loader:901:27)\n at Module.require
(node:internal/modules/cjs/loader:1115:19)\n at require
(node:internal/modules/helpers:130:18)\n
Looks like its trying to resolve an import via esm/resolve helper, then falling back to cjs loader and then failing.. ./node_modules/@auth/core does contain the full package and normal package.json with the export map as expected, of course theres no main export, but it should pick up the import key :thinking:
My fastify application is rather simple/straightforward atm and all ESM as far as I can tell
"type": "module"in root package.json- using
importeverywhere - Running it with
tsx watch src/index.ts
Here's my entrypoint file, I've checked out your branch, built the fastify adapter and pnpm link-ed it into my project to get this:
import Fastify from "fastify"
import { dirname, join } from "path"
import { fileURLToPath } from "url"
import { updateJob } from "@jobs/cron-update"
import autoLoad from "@fastify/autoload"
import formbodyParser from "@fastify/formbody"
import GitHub from "@auth/fastify/providers/github"
import { FastifyAuth } from "@auth/fastify"
const fastify = Fastify({ logger: { level: "warn" } })
const _dirname = dirname(fileURLToPath(import.meta.url))
fastify.register(formbodyParser)
fastify.register(
FastifyAuth({
providers: [
GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
}),
{ prefix: "/api/auth" },
)
fastify.register(autoLoad, {
dir: join(_dirname, "routes"),
})
fastify.register(autoLoad, {
dir: join(_dirname, "plugins"),
})
;(async function () {
const port = process.env.PORT ? parseInt(process.env.PORT) : 8000
try {
await fastify.listen({ port, host: "0.0.0.0" })
console.log(`
๐ Server ready at: http://0.0.0.0:${port}
โ Next cron run at: ${updateJob.nextRun()}
`)
} catch (err) {
fastify.log.error(err)
process.exit(1)
}
})()
Am I missing anything? What did your example / development application look like and how did you run it? Maybe this is just a tsx issue :thinking:
@ndom91 Here is a demo: https://github.com/hillac/authjs-fastify-demo I'm not 100% if I've done the auth decorator in the idiomatic way, but it works for me. I'm on node 20.9.0.
Also, as was discussed with @auth/express, it might be nicer for users if the auth decorator is part of @auth/fastify in the plugin. I left it out for now to copy @auth/express.
@hillac okay great, thanks for the repo example. Turns out I was just having some issues with the fastify autoload plugin. Seems to all work now!
Regarding your question about the decorator - while I agree, we should try to get as much as possible in the plugin itself, this "authenticate" decorator seems like it would potentially be very different from user to user, no? :thinking:
Also looks like the toFastifyReply behaviour was correct when using an async cb - https://fastify.dev/docs/latest/Reference/Routes/#promise-resolution
Codecov Report
Attention: Patch coverage is 94.40789% with 17 lines in your changes missing coverage. Please review.
Project coverage is 39.63%. Comparing base (
0b94eab) to head (302eb31). Report is 1 commits behind head on main.
Additional details and impacted files
@@ Coverage Diff @@
## main #9587 +/- ##
==========================================
+ Coverage 39.07% 39.63% +0.55%
==========================================
Files 189 193 +4
Lines 29753 30057 +304
Branches 1281 1313 +32
==========================================
+ Hits 11625 11912 +287
- Misses 18128 18145 +17
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
No dependency changes detected. Learn more about Socket for GitHub โ๏ธ
๐ No dependency changes detected in pull request
Should we register @fastify/formbody in the plugin, and include it as a peer dependency? This would make setup easier.
Should we register @fastify/formbody in the plugin, and include it as a peer dependency? This would make setup easier.
Yeah so this is to auto-parse x-www-form-urlencoded bodies correctly, right?
I figure since this is an authentication library backend, folks will be pointing forms (username / password, or just email address with OAuth provideres, etc.) at it often. So makes sense to me :+1:
Yeah so this is to auto-parse
x-www-form-urlencodedbodies correctly, right?
Yep
I figure since this is an authentication library backend, folks will be pointing forms (username / password, or just email address with OAuth provideres, etc.) at it often. So makes sense to me ๐
Yeah, it is definitely needed since the oauth sign in request has to come from a html form in my experience. The redirect fails with an AJAX request.
I made it a peer dependency but thoughts on whether it should just be a normal dependency?
Yeah I mean if its going to fail in its current state without it installed, I'd say go for it.
What I was implying in my previous comment also is that this doesn't seem like a throw-away dependency, it adds value and seems like it will be useful more than just in this one use-case if folks are integrating form-based sign-in with their Fastify backend, ya know.
I tried to find is there someone are doing integration with Fastify. Finally, found this PR. I'm looking forward for Auth.js support with Fastify.
- 1
Any updates on this PR?
Are there any updates on this PR? Looking forward to seeing this move forward to use it in my production apps.
I think it's ready, just waiting for a review I guess.
Hey yeah this looks pretty good already! We'd really appreciate some prep in the new docs for Fastify as well
Can you add support for a fastify docs tabs for in the /docs/../Code/index.tsx component? (see: https://github.com/nextauthjs/next-auth/blob/main/docs%2Fcomponents%2FCode%2Findex.tsx).
Also maybe then add fastify example tabs to some of the initial setup docs / code examples in pages like /docs/pages/getting-started/installation.mdx and session-management/*.mdx? Once you add support for the fastify Code tab component (i.e. Code.Fastify), itll be super straight forward to add a fastify Tab in those docs pages
The information in the getting started pages seems kind of redundant to the information already in the api reference page.
Ok, everything's ready
Is anything else needed to get this through?
Is anything else needed to get this through?
any updates on this?
@ndom91 @balazsorban44 @ThangHuuVu Anything else needed to get this merged?
Oops, didn't realize renaming a branch would close the PR
When is it expected to be published/approved?
When is it expected to be published/approved?
@ndom91 @balazsorban44 @ThangHuuVu
Sorry didn't mean to totally approve yet, just wanted to remove my one "requested changes" instance.
Anyway, it does look pretty good already! I just committed a few small tweaks.
What I'd like to see in order to merge this is at least an example app under apps/examples/fastify/* (see Qwik PR as example here). That's something we'll then sync automatically to a separate next-auth/fastify-auth-example repository (see .github/sync.yml). From there we can deploy that example app to a vercel demo project to point folks to.
And then beyond that we also like to have a separate dev app under apps/dev/fastify/* which doesn't have to be nice looking and doesn't get synced anywhere, it can also point to the workspace:* versions of everything (as opposed to the actual release versions in the example app(s)) and we can use to run in development to actually exercise all the @auth/fastify functionality.