This content originally appeared on DEV Community and was authored by Thomas Ledoux
I've been wanting to add authentication to my personal website for a while now to see how it works in Astro.
With Prisma and PlanetScale already running for comments on my blogs, I decided to store my account information in PlanetScale.
Because it's just used for my own account, and I'm not storing any other sensitive information in my database, I decided to store the credentials in plain text for now.
I changed my Prisma schema to make this possible:
// schema.prisma
model Account {
id Int @id @default(autoincrement())
username String @unique
password String
}
Once the model is updated in the code, running npx prisma db push
propagates the changes to PlanetScale, so the schema is updated in the actual database.
I used an existing package called @astro-auth
to handle all the authentication on my site.
For this to work, I needed to add 2 environment variables to my application: ASTROAUTH_URL
(the URL my site is hosted on) and ASTROAUTH_SECRET
(a self chosen secret key).
Because I stored the credentials in PlanetScale, I needed to use the CredentialProvider
to enable logging in with username and password.
There are many other providers available on @astro-auth
, go check out the package if you're interested.
The code needed to set this up with @astro-auth
looks like this:
// /pages/api/auth/[...astroauth].ts
import AstroAuth from '@astro-auth/core';
import { CredentialProvider } from '@astro-auth/providers';
import { prisma } from '../../../lib/prisma';
export const all = AstroAuth({
authProviders: [
CredentialProvider({
authorize: async properties => {
const account = await prisma?.account.findFirst({
where: {
username: properties.username,
AND: {
password: properties.password,
},
},
select: {
id: true,
},
});
if (account?.id) {
return properties.username;
}
return null;
},
}),
],
});
Creating a login page was very easy.
I just created a form, calling the signIn()
method from @astro-auth
on submit, and BOOM: logged in!
The code for the login page:
// /pages/login.astro
<html>
<head>
<title>Login</title>
<script>
import { signIn } from '@astro-auth/client';
document.addEventListener('DOMContentLoaded', () => {
document.querySelector('form')?.addEventListener('submit', async e => {
e.preventDefault();
const form = e.target;
if (form) {
const formData = new FormData(form as HTMLFormElement);
const data = Object.fromEntries(formData);
await signIn({
provider: 'credential',
login: data,
});
window.location.href = '/';
}
});
});
</script>
</head>
<body>
<form>
<label for="username">Name</label>
<input type="text" name="username" />
<label for="password">Password</label>
<input type="password" name="password" />
<input type="submit" value="Submit" />
</form>
</body>
</html>
After submitting the form, the user's signed in and is redirected to the homepage.
Protecting a page with authentication is easy, just checking the logged in user with the getUser()
function from @astro-auth
.
Here's an example of a page where I used this check:
// /pages/comment-overview.astro
---
import { getUser } from '@astro-auth/core';
import Layout from '../layouts/Layout.astro';
import { prisma } from '../lib/prisma';
import CommentsOverviewWrapper from '../components/CommentOverviewWrapper';
const user = getUser({ client: Astro });
if (!user) {
return Astro.redirect('/', 307);
}
const commentsWithPost = await prisma?.comment.findMany({
include: {
post: {
select: {
url: true,
},
},
},
});
---
<Layout
description="Overview of comments"
title="Thomas Ledoux | Comment overview"
>
<CommentsOverviewWrapper commentsWithPost={commentsWithPost} client:load />
</Layout>
If the user is not logged in, the user will be redirected to the homepage with a 307 status code.
I also have an API route to delete comments on my blog posts, which I want to fence off so only authenticated user can use this API.
It's possible to use the getUser()
function from @astro-auth
for this too, but this time we're going to pass the request
instead of the Astro
object.
Example of using this code:
// /pages/api/comments.ts
export const del: APIRoute = async ({ request }) => {
const user = getUser({ server: request });
if (user) {
const body = await request.json();
const deleteComment = await prisma?.comment.delete({
where: {
id: body.id,
},
});
return new Response(
JSON.stringify({
message: `Comment with id ${deleteComment?.id} deleted`,
}),
{ status: 200 }
);
}
return new Response(null, { status: 403 });
};
So when the user is not authenticated, a 403 response will be returned.
Hope this was helpful!
Source code can be found on my Github as always.
This content originally appeared on DEV Community and was authored by Thomas Ledoux
Thomas Ledoux | Sciencx (2023-02-26T16:22:26+00:00) Setting up authentication in Astro with Prisma and Planetscale. Retrieved from https://www.scien.cx/2023/02/26/setting-up-authentication-in-astro-with-prisma-and-planetscale/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.