Background
- I wanted to implement a feature in Astro that allows authentication with an email and password and stores session information in cookies.
- It seemed possible with auth-astro, which is officially introduced in Astro.
- auth-astro is a wrapper library that makes Auth.js (formerly NextAuth.js) easier to use in Astro.
- I couldn't find consolidated information for the following two points, so I organized them:
- How to set up password authentication.
- How to customize the authentication information saved in cookies.
Important Note
After trying this, I found Auth.js to be somewhat difficult to use, so it's worth considering carefully before deciding to use it. Refer to the Impressions section for details.
Environment
- Node.js: 20.11
-
astro
: 4.15.2 -
auth-astro
: 4.1.2 -
@auth/core
: 0.35.3
※ In auth-astro 4.1.2, "@auth/core": "^0.32.0"
is specified in the dependencies, but version 0.32.x
had an issue where a 500 error occurred during error handling for CredentialsSignin
, so I specified the latest version.
Final Code
Assuming that auth-astro has already been integrated into a project created with Astro, you can set up password authentication and customize session information by configuring auth.config.mjs
as follows. The example below saves the logged-in user's ID as a session token in cookies.
// auth.config.mjs
import { CredentialsSignin } from "@auth/core/errors";
import Credentials from "@auth/core/providers/credentials";
import { defineConfig } from "auth-astro";
export default defineConfig({
providers: [
Credentials({
name: "Email",
credentials: {
email: { type: "email", required: true, label: "Email" },
password: { type: "password", required: true, label: "Password" },
},
authorize: async (credentials) => {
const { email, password } = credentials;
const user = await authorize(email, password); // your logic here
if (!user) throw new CredentialsSignin();
return { id: user.id };
},
}),
],
callbacks: {
async jwt({ user, token }) {
if (user) {
return { id: user.id };
}
return token;
},
session({ session, token }) {
return {
...session,
user: { id: token.id },
};
},
},
});
UI
Using the login screen provided by Auth.js, the following behavior can be seen:
Login Screen (/api/auth/signin)
When Authentication Fails (/api/auth/signin?error=CredentialsSignin&code=credentials)
When Authentication Succeeds
After successful authentication, you are redirected to another page, and you can retrieve session information using getSession
.
// pages/example.astro
---
import { getSession } from 'auth-astro/server';
const session = await getSession(Astro.request)
console.log(session)
// {
// user: { id: 1 },
// expireds: '2024-10-01T00:00:00.000Z'
// }
---
{session?.user ? (
<p>Logged in as User {session.user.id}</p>
) : (
<p>Not logged in</p>
)}
When authentication is successful, a cookie named authjs.session-token
is saved, with its contents encrypted in JWT format by Auth.js.
Example of a Session Token
eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwia2lkIjoiTjEzYW5aS3hzMmpXT0pDUmNYaTRCYjZCMkdUNmxZdmxaa3lUVEdfbHRYZG14UEJVYkVkNkJxaVlRRnhNdTFuZjRBa3BGRmxhTlI1dW11ZENRc3JaQ0EifQ..IAkEwYkG1M9Y-J5rTju63g.haLgQeSxUEqctr2Gjn23sMaNEAaGb9y1ott-dZnws-oo7Sdcz1fnPRRIvpLpBe6LkJN2JMELAHbElbAl-41BrgAzUDTeWnGlNTeFVa3Em6iWObNtfwX_H7nOtnddB3OZ.aOZQ_jzdVkNvdepksoAFc9I8Qz7fuFlcAtJcN-Tp-ng
Customizing the Session Token
Default Behavior
When using Credentials, the default behavior is to save email
as the unique key in the cookie. If you don't need to change the format, customization of the callbacks is unnecessary. The following settings would suffice.
// auth.config.mjs
import { CredentialsSignin } from "@auth/core/errors";
import Credentials from "@auth/core/providers/credentials";
import { defineConfig } from "auth-astro";
export default defineConfig({
providers: [
Credentials({
name: "Email",
credentials: {
email: { type: "email", required: true, label: "Email" },
password: { type: "password", required: true, label: "Password" },
},
authorize: async (credentials) => {
const { email, password } = credentials;
const user = await authorize(email, password); // your logic here
if (!user) throw new CredentialsSignin();
return { email: user.email };
},
}),
],
});
Contents of the session
const session = await getSession(Astro.request)
console.log(session)
// {
// user: { email: 'test@example.com', name: undefined, image: undefined },
// expireds: '2024-10-01T00:00:00.000Z'
// }
Customization Details
If you want to save information other than the email in the session token, you will need to specify the callbacks. The example below replaces the email with the user ID. This is reflected in the code shown in the "Final Code" section of this article.
authorize: async (credentials) => {
// ...
return { id: user.id }; // Change the return format
},
callbacks: {
// Change the token format saved in the cookie
async jwt({ user, token }) {
if (user) {
return { id: user.id };
}
return token;
},
// Change the format of the information read from the token
session({ session, token }) {
return {
...session,
user: { id: token.id },
};
},
},
In this case, you can retrieve the session in the following format:
const session = await getSession(Astro.request)
console.log(session)
// {
// user: { id: 1 },
// expireds: '2024-10-01T00:00:00.000Z'
// }
Impressions
The feature I wanted to implement, "password authentication with email and saving session information in cookies with Astro," was achieved with the above configuration.
However, after working with Auth.js, I felt that customizing its behavior might be challenging.
For example, in the following cases, there is no official support, so you would likely need to prepare a custom page and implement it yourself:
- If you want to change error messages (for multilingual support, etc.)
- If you want to modify the appearance of the login page
Also, keeping the email address entered in the field after an authentication failure seems quite difficult with the current Auth.js system. Since there is a redirect during the authentication process, the information entered in the form is lost.
In my research, I also came across the following opinions:
-
You're not a bad engineer, NextAuth is a bad library. - Reddit
Conversely, it feels like NextAuth was written with the express goal of avoiding the complexity of auth, and it does it badly. It lets you get something working quickly, but later causes immense pain trying to customise it.
-
Why is next-auth (or Auth.js) so popular? - Reddit
I have tried to build a new website with auth, and my experience with Auth.js (v5) was nothing short of a disaster. The docs was horrible, it offers little customizability, and the configuration just doesn't work. If I were the project lead, I wouldn't promote this piece of shit until it gets stable.
If you're using the default behavior of Auth.js, there might not be any issues, but if you're considering any customizations, it might be worth reviewing beforehand.
References
Authentication | Astro
https://docs.astro.build/en/guides/authentication/#authjsnowaythatworked/auth-astro: Community maintained Astro integration of @auth/core
https://github.com/nowaythatworked/auth-astroCredentials | Auth.js
https://authjs.dev/getting-started/authentication/credentialsCallbacks | NextAuth.js
https://next-auth.js.org/configuration/callbacks
Top comments (0)