At the time of writing this article follows the latest version of
next-authwhich 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
authandsigninmethods then it's likely because you need to disablecompilerOptions.declarationandcompilerOptions.declarationMapin the.tsconfigfile.If you setup your project with
turborepothen 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
nullhowever this causes issues with the auth flow ofnext-auth, similarly if your error doesn't include akindproperty 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