Credentials Authentication with Database Sessions Using Auth.js, SvelteKit and Prisma
Tags: svelte, prisma, authjs
Published: 2023-01-31 08:32:18

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
	}
});
```