Hello!
I want to use next-auth with an existing DB connection (TypeORM, MySQL) since it handles migrations, handles HMR in dev mode and already configured properly. I鈥檓 going to extend User as well.
Do I have to write a custom adapter in this case? Or are there other ways?
I鈥檓 going to use next-auth v3.
Thanks!
Hi Yuri!
Going v3 is a good idea, it should be out very soon and I don't think here will be much more changes between the current beta and release (possibly error page handling).
Your question is good but a little tricky to answer, I'll do my best to provide the most useful information so you can decide the best route.
I would say use the built-in provider and extending the model is ideal if you can, however if you have an existing database and models I appreciate it might not be viable; and it might be easier to write an adapter (though that is also a bit of a pain to do).
Another option is to not specify a database at all for NextAuth.js and instead use the signIn() callback to look up users and either sign them in (if they have accounts already) or create a new account for them in your database before returning the callback.
It is hard to say if that is better or worse idea without knowing your set up (e.g. what combination of authentications services you want to support). If you only intended to support one or two sign in services (or credentials based sign in - e.g. username / password) then it's a great approach, but if you need to support multiple providers then it might be more difficult (and writing an adapter might be easier).
Hi Iain!
Thank you for your answer! I'll try to write here about my solution (for anyone who is facing the same task).
It is hard to say if that is better or worse idea without knowing your set up
I plan to use a traditional email+password login as well as logins through several social networks. But I would like to do that in the most universal way though, if possible.
Some thoughts now: the first thing you want to do when you want to reuse the existing connection is passing existing connection object as a database parameter:
import { ensureConnection } from '../core/database';
const nextAuthOptions = {
// ...
database: ensureConnection(),
}
This connection, for sure, must already have corresponding entities (User, Account, Session, etc.) registered. And then providing your customised entities can be done, for example, in such way (or something similar):
import { ensureConnection } from '../core/database';
import { User } from '../entities/User';
const nextAuthOptions = {
// ...
database: ensureConnection(),
models: {
user: User,
}
}
Don't you want to make NextAuth providing such functionality out-of-the box in a convenient way? I believe extending users, adding new properties to them, is a common enough task and can be highly welcomed, as well as the reusing of an existing connection.
If you think this is worth to be implemented (and maybe not-so-hard) we could think about the details. For example, we should consider HMR in next dev mode, when entity (for example, User), can change due to recompilation (this is not so hard at all).
Ok... I think that when we use custom models, for example, User, in next dev mode they can change thanks to an automatic recompilation under the hood (HMR). I don't think that NextAuth should handle this inside itself. This can be handled outside. So an interface similar to this can be used to solve this (just an example, do not take it strictly):
import { ensureConnection } from '../core/database';
import { User } from '../entities/User';
async function provideDatabase() {
return {
connection: await ensureConnection(),
models: {
user: User,
}
}
}
const nextAuthOptions = {
database: provideDatabase,
}
Function provideDatabase() should provide always relevant entity classes (if they are customised) and an updated TypeORM connection.
(Just ideas, I do not insist in any way :) You really know better what is better for the lib)
As per above, it already possible to use custom models with NextAuth.js but there isn't a tutorial for it yet.
The existing models are exported and can be modified, or you can pass in your own. For anyone curious, it should be possible to work out how to do it if you poke around in the TypeORM adapter code. It would be great to have a tutorial for this.
There is more detail in #283 and other related issues.
This is possible with a custom adapter but is not supported by the default adapter, as you note.
There are quite a few issues with supporting connection sharing and limited upsides with serverless.
With serverless, every route is (typically) as separate function that maintains it's own connection to the database - there is no global connection pool as there is in a monolithic application, so there is no performance optimisation from using a shared connection object.
If you pass your own connection, you need to have robust connection state handling. This is tricker with serverless than it is with monolithic applications, as you have to worry about more factors (e.g. functions getting suspended, functions unexpectedly terminating early leading to stale connections, etc.)
If you pass your own connection, you have to make sure you initialize it with the options NextAuth.js expects or it won't work correctly, e.g. else sessions will expire at the wrong time (or sign in won't at all) and email sign in won't work.
We wouldn't want to set these automatically on a _shared_ connection that was being re-used (e.g. in a monolithic application) as it would mess up all the timestamps written by other queries using that same shared connection.
Supporting passing in a shared connection adds complexity and invites people to write their own connection handling logic which I would guess would lead to a greater support overhead for us and more pain for them and I don't think that's a road I want to go down.
If you think this is worth to be implemented (and maybe not-so-hard) we could think about the details. For example, we should consider HMR in next dev mode, when entity (for example, User), can change due to recompilation (this is not so hard at all).
I think supporting Hot Module Reloading of arbitrary models, with subsequent implications of updating of table schemas on the fly across MySQL, Postgres and MongoDB is not one I'm particularly interested in writing code to support personally (given complexity, value and other things I could be doing).
In the case of NextAuth.js we'd want to consider if / how we support this in a compatible way for all the databases NextAuth.js supports - and how you would handle things like type transforms for custom models in this scenario (as TypeORM is not a complete abstraction; we already have to do some of the work ourselves by transforming models at run time).
We also need to factor in who is going to write this code and stick around to support it (create and maintain tests, TypeScript definitions, documentation, etc).
A good way to approach tackling this is probably create a custom adapter by forking the existing adapter code and passing that adapter into NextAuth.js. You could even publish it as an NPM module (something we have discussed doing with our own adapters over time, as the list of built-in adapters starts to grow) to share with folks - we'd be very happy to feature it!
I'm inclined to wait until TypeORM actually supports HMR out of the box before looking at adding it to NextAuth.js but I would be very happy to test out a PR or custom adapter that supported it.
Thanks, Iain, for your detailed reply.
A good way to approach tackling this is probably create a custom adapter by forking the existing adapter code and passing that adapter into NextAuth.js. You could even publish it as an NPM module (something we have discussed doing with our own adapters over time, as the list of built-in adapters starts to grow) to share with folks - we'd be very happy to feature it!
I'll try to delve into this soon :)
I was about to create another issue but I think this is directly connected to this.
The setup
I have multiple models setup with typeorm like I already mentioned in issue283.
In my ormconfig file, I declare my entities the following way so HMR works like plashenkov suggested:
import * as entities from "@modules/entities";
const typeOrmConfig = {
...
synchronize: true,
entities: ['src/api/modules/**/*.entity.ts']
}
My user table is the only one currently overlapping with next-auth models, but anyway I don't reach the point where I could get errors.
The problem
When I either drop all tables, go to one route so tables are regenerated or manually drop and sync tables with yarn, only the tables declared in my ormconfig.ts entities are created.
I tried to export next-auth models as account.entity.ts... so they are loaded by the orm but to no avail.
I think this is due to what you say about connection sharing. I handle the connection to my postgres instance of my graphQL API myself so I guess this problem is normal with the current adapter.
Extending methods
We can now extend models and add fields to schemas which is great.
But, let's say I add a field interests to the user table and during onboarding the user is prompted to select a list of his interests. When he registers, I'd like to set his interests field with the user input, or even do some other stuff.
So I would need to modify/extend createUser. As we can see in typeorm/index.js:
async function createUser (profile) {
debugMessage('CREATE_USER', profile)
try {
// Create user account
const user = new User(profile.name, profile.email, profile.image, profile.emailVerified)
return await getManager().save(user)
} catch (error) {
logger.error('CREATE_USER_ERROR', error)
return Promise.reject(new CreateUserError(error))
}
}
The fields declared in the user model are here hardcoded, thus if I extend the model, it won't include my new fields.
Did I miss something or extending methods are currently not possible? Will it be possible in the future?
If I may suggest, having an example with custom adapters and showing how to extend everything would be a great addition.
@plashenkov did you make any progress on your next.js/next-auth integration or ended up creating your own auth system?
@trompx Hmm, adding additional fields at signup specifically isn't something that is currently supported.
The newUser custom page (which new accounts are directed to after signing in for the first time, if specified) and the redirect callback can be used to steer a user to a page to 'complete' a sign up process.
Explicitly requiring fields _before_ an account can be created is not currently part of the flow for OAuth - and is probably not something we would support practically - at least, I don't think any time soon, just given other issues backlog, the amount of work required and that it's possible to work around it (e.g. by adding something to the session object or JWT that redirects the user to a page to complete the sign up process).
You are not the first person to ask about being able to set additional properties on sign in, it's just a little challenging to support in a generic way (e.g. that works with all OAuth providers).
If you would like to capture this as a specific enhancement, please feel free - I can't promise we will do it, but I think it would be helpful to at least shake out ways of supporting this behaviour!
Maybe it's something we could better support if we have more sophisticated callback behaviour - where they could sign in and create an account, but not actually use the account until the sign up process was deemed complete.
In the short term, a tutorial showing how to achieve something like this (and the limitations of what is currently possible) sounds achievable.
Your point about requiring additional data after signup (for oauth compatibility) totally makes sense and made me reconsider my auth design process. Thanks for the answer and detail explanation Iain, really appreciate.
Due to my difficulties to implement next-auth with my current setup, I will roll my own auth system for the time being but will keep an eye on next-auth which looks truly promising. Wish your the best!
@plashenkov did you make any progress on your next.js/next-auth integration or ended up creating your own auth system?
It's hard to understand how to customize next-auth to special needs due to some nuances are not documented. I think I'll go with Passport.js from scratch. So, let's close this issue.
Thanks Iain and trompx for your participation and answers!
Most helpful comment
Thanks, Iain, for your detailed reply.
I'll try to delve into this soon :)