This guide will show you how to get started with implementing credentials authentication using Auth.js, Sveltekit, and Prisma, while utilizing database sessions.
Step 1: Installing Dependencies
First, you'll need to install the necessary packages. In your Sveltekit project, run the following commands:
pnpm i prisma @prisma/client @auth/core @auth/sveltekit bcrypt
Step 2: Create your Prisma Schema
Create a file named schema.prisma and paste the following code:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL") // Only needed when using a cloud provider that doesn't support the creation of new databases, like Heroku. Learn more: https://pris.ly/migrate-shadow
}
generator client {
provider = "prisma-client-js"
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
username String? @unique
password String?
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}
Step 3: Create the database schema with Prisma Migrate
Run the following command to create the database schema:
npx prisma migrate dev
This will create an SQL migration file and execute it.
Note that you will need to specify your database connection string in the environment variable DATABASE_URL
. You can do this by setting it in a .env
file at the root of your project.
Step 4: Initialize the Prisma client
Create a file named client.ts in the src/lib/prisma directory and paste the following code:
// src/lib/prisma/client.ts
import { PrismaClient } from '@prisma/client';
import { NODE_ENV } from '$env/static/private';
declare global {
// eslint-disable-next-line no-var
var prisma: PrismaClient | undefined;
}
export const prisma =
global.prisma ||
new PrismaClient({
// * Uncomment this to see the SQL queries in the console
// log: NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error']
});
if (NODE_ENV !== 'production') {
global.prisma = prisma;
}
Step 5: Set up the SvelteKit Server Hooks
In this step, you will create a server hooks file in your SvelteKit project to handle authentication with @auth/sveltekit. You will also import the necessary dependencies such as CredentialsProvider from @auth/core, PrismaAdapter from @next-auth/prisma-adapter, and bcrypt for password encryption.
// src/hooks.server.ts
import { SvelteKitAuth } from '@auth/sveltekit';
import CredentialsProvider from '@auth/core/providers/credentials';
import type { Handle } from '@sveltejs/kit';
import { PrismaAdapter } from '@next-auth/prisma-adapter';
import type { Adapter } from '@auth/core/adapters';
import { prisma } from '$lib/prisma/client';
import type { Provider } from '@auth/core/providers';
import { encode, decode } from '@auth/core/jwt';
import bcrypt from 'bcrypt';
export const handle: Handle = async ({ event, resolve }) => {
const result = await SvelteKitAuth({
adapter: PrismaAdapter(prisma) as Adapter,
providers: [
CredentialsProvider({
// The name to display on the sign in form (e.g. "Sign in with...")
name: 'Credentials',
// `credentials` is used to generate a form on the sign in page.
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
// e.g. domain, username, password, 2FA token, etc.
// You can pass any HTML attribute to the <input> tag through the object.
credentials: {
username: { label: 'Username', type: 'text', placeholder: 'Username' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials) {
if (!credentials) {
return null;
}
const user = await prisma.user.findUnique({
where: {
username: credentials.username
}
});
if (!user || !user.password) {
return null;
}
const isPasswordValid = await bcrypt.compare(credentials.password, user.password);
if (!isPasswordValid) {
return null;
}
return user;
}
})
] as Provider[],
callbacks: {
async signIn({ user, credentials }) {
// Check if this sign in callback is being called in the credentials authentication flow.
// If so, use the next - auth adapter to create a session entry in the database
// (SignIn is called after authorize so we can safely assume the user is valid and already authenticated).
if (credentials && credentials.username && credentials.password) {
if (user) {
const sessionToken = crypto.randomUUID();
// Set expiry to 30 days
const sessionExpiry = new Date(Date.now() + 60 * 60 * 24 * 30 * 1000);
await PrismaAdapter(prisma).createSession({
sessionToken: sessionToken,
userId: user.id,
expires: sessionExpiry
});
event.cookies.set('next-auth.session-token', sessionToken, {
expires: sessionExpiry
});
}
}
return true;
}
},
jwt: {
// Add a callback to the encode method to return the session token from a cookie
// when in the credentials provider callback flow.
encode: async (params) => {
if (
event.url.pathname?.includes('callback') &&
event.url.pathname?.includes('credentials') &&
event.request.method === 'POST'
) {
// Get the session token cookie
const cookie = event.cookies.get('next-auth.session-token');
// Return the cookie value, or an empty string if it is not defined
return cookie ?? '';
}
// Revert to default behaviour when not in the credentials provider callback flow
return encode(params);
},
decode: async (params) => {
if (
event.url.pathname?.includes('callback') &&
event.url.pathname?.includes('credentials') &&
event.request.method === 'POST'
) {
return null;
}
// Revert to default behaviour when not in the credentials provider callback flow
return decode(params);
}
},
session: {
strategy: 'database',
maxAge: 60 * 60 * 24 * 30, // 30 days
updateAge: 60 * 60 * 24, // 24 hours
generateSessionToken: () => {
return crypto.randomUUID();
}
}
})({
event,
resolve
});
return result;
};
Signing up users
Creating a user account is quick and effortless when you use Prisma. With just a few lines of code, you can add new users to your database and get them started in no time. Take a look at the code below:
const user = await prisma.user.create({
data: {
username: username,
password: await bcrypt.hash(password, 10)
}
});
const account = await prisma.account.create({
data: {
userId: user.id,
type: 'credentials',
provider: 'credentials',
providerAccountId: user.id
}
});
Top comments (1)
since I'm just starting out with this type of programming, the part, which I think is missing, relating to protected pages and user registration/authentication would also be useful.
Is there any link that explains it in simple way?