At the time of writing this article follows the latest version of
next-auth
which is version5.0.0-beta.19
.
Whilst working on a project that required authentication, I evaluated a range of libraries. Auth.js (formerly next-auth) stood out as it allows user's to own their data in addition to having community packages for other frameworks such as hono.js.
Generally I was impressed with the library, despite being in a beta state it provides a significant amount of functionality with the ability to extend core features.
Unfortunately I faced some challenges when implementing a Credentials' authentication method (e.g. username and password) primarily because the documentation is outdated.
The following is my journey to setup credentials authentication using Auth.js beta.
Install dependencies
pnpm add next-auth@beta
Generate encryption key
The key is used in order to encrypt tokens and verification hashes
npx auth secret
The above command with add an AUTH_SECRET=
entry to the .env.local
file.
Create auth.ts file
In my project I don't use the src
folder so placed the file auth.ts
at the
root of the application; however if you had a src
folder then you'd likely
want to nest it under this directory.
// auth.tsimport NextAuth from 'next-auth'export const { handlers, signIn, signOut, auth } = NextAuth({secret: process.env.AUTH_SECRET!,providers: []})
Note: if receive issues for
auth
andsignin
methods then it's likely because you need to disablecompilerOptions.declaration
andcompilerOptions.declarationMap
in the.tsconfig
file.If you setup your project with
turborepo
then it's likely you will need to set the above settings to false.{"compilerOptions": {"declaration": false,"declarationMap": false}}
Add authentication routes
We want to allow next-auth to handle sign-in requests; in order to do this we
declare a dynamic route. The next-auth docs typically places this under the
/api
route however I prefer to place this at the root. For example
http://localhost:3000/auth/sigin
.
At the following location /app/auth/[...nextAuth]
we want to create the
following file routes.ts
// app/auth/[...nextAuth]/route.tsimport { handlers } from '@/auth'export const { GET, POST } = handlers
Note: that my project has an alias to resolve
@/
to the root of the project.
We will then want to update auth.ts
to define a baseUrl
of '/auth'
Local credentials' provider
Inside out auth.ts
file we want to add an import to the Credentials
provider
import Credential from 'next-auth/providers/credentials'
Invalid credential error object
We will need to define an error type to handle an invalid credential error. It's
important to define a kind
on the error. Otherwise, the built-in error state
won't work.
class InvalidCredentialError extends AuthError {public readonly kind = 'signIn'constructor() {super('Invalid credentials')this.type = 'CredentialsSignin'}}
Credentials authentication configuration
Once we have the error object defined we will want to create a provider in order to authenticate a user.
We define a credentials
object that defines a text field for each of the
required inputs.
Following this we define a authorize
method in order to handle the
authentication request. It's important to note that if we are unable to
authorize a user, we need to throw the error defined in the previous step.
The documentation says you can return
null
however this causes issues with the auth flow ofnext-auth
, similarly if your error doesn't include akind
property that has a value ofsignin
.
Credential({credentials: {email: { label: 'Username' },password: { label: 'Password', type: 'password' },},authorize: async (credentials) => {if (credentials.email === 'someone@example.com') {return { id: 'xxx', email: credentials.email }}throw new InvalidCredentialError()},name: 'credentials',type: 'credentials',}),
Final result
Once we've performed all these steps, you should expect to have the following files and their contents:
// auth.tsimport NextAuth from 'next-auth'import Credential from 'next-auth/providers/credentials'class InvalidCredentialError extends AuthError {public readonly kind = 'signIn'constructor() {super('Invalid credentials')this.type = 'CredentialsSignin'}}export const { handlers, signIn, signOut, auth } = NextAuth({secret: process.env.AUTH_SECRET!,basePath: '/auth',providers: [Credential({credentials: {email: { label: 'Username' },password: { label: 'Password', type: 'password' },},authorize: async (credentials) => {if (credentials.email === 'someone@example.com') {return { id: 'xxx', email: credentials.email }}throw new InvalidCredentialError()},name: 'credentials',type: 'credentials',}),]})
// app/auth/[...nextAuth]/route.tsimport { handlers } from '@/auth'export const { GET, POST } = handlers
The official setup guide can be found at Auth.js | InstallationGitHubVercel