@peopleplus/auth v0.3.5
PeoplePlus Auth
This package builds on top of lucia auth to provide handlers with preconfigured behaviour that is in line with the requirements of most if not all apps we maintain and build at PeoplePlus.
Adding to a project
Tables
Depending on what database backend you are using you may need to add the following to your schema:
classDiagram
User <|-- Key: user_id
User <|-- Session: user_id
class User {
id: string
...user attributes
}
class Key {
id: string
user_id: string
hashed_password: string | null
}
class Session {
id: string
user_id: string
active_expires: int
idle_expires: int
id_token: string
access_token: string
}
Here's an example drizzle schema:
import { bigint, pgTable, text, varchar } from 'drizzle-orm/pg-core';
export const usersTable = pgTable('user', {
id: varchar('id', { length: 15 }).primaryKey(),
});
export const sessionsTable = pgTable('session', {
id: varchar('id', { length: 128 }).primaryKey(),
userId: varchar('user_id', { length: 15 })
.notNull()
.references(() => usersTable.id),
activeExpires: bigint('active_expires', { mode: 'number' }).notNull(),
idleExpires: bigint('idle_expires', { mode: 'number' }).notNull(),
accessToken: text('access_token').notNull(),
idToken: text('id_token').notNull(),
});
export const userKeysTable = pgTable('user_key', {
id: varchar('id', { length: 255 }).primaryKey(),
userId: varchar('user_id', { length: 15 })
.notNull()
.references(() => usersTable.id),
hashedPassword: varchar('hashed_password', { length: 255 }),
});
Set Up
Install this package alonside lucia
npm i -D lucia @peopleplus/auth
Create a file at $lib/server/auth.ts
with content similar to this:
import { dev } from '$app/environment';
import { lucia } from 'lucia';
import { sveltekit } from 'lucia/middleware';
import { postgres as postgresAdapter } from '@lucia-auth/adapter-postgresql';
import { queryClient } from './database';
import peoplePlusAuth from '@peopleplus/auth';
import {
PRIVATE_AUTH0_API_IDENTIFIER,
PRIVATE_AUTH0_CLIENT_ID,
PRIVATE_AUTH0_CLIENT_SECRET,
PRIVATE_AUTH0_DOMAIN,
} from '$env/static/private';
import { CosmosClient } from '@cfworker/cosmos';
export type Auth = typeof auth;
const auth = lucia({
env: dev ? 'DEV' : 'PROD',
middleware: sveltekit(),
// Swap out for whatever adapter you're using
adapter: postgresAdapter(queryClient, {
user: 'user',
session: 'session',
key: 'user_key',
}),
});
export const { handleAuthCallback, handleSignInRedirect, handleSignOut, authenticationHook } =
peoplePlusAuth({
auth,
domain: PRIVATE_AUTH0_DOMAIN,
clientID: PRIVATE_AUTH0_CLIENT_ID,
clientSecret: PRIVATE_AUTH0_CLIENT_SECRET,
audience: PRIVATE_AUTH0_API_IDENTIFIER,
urls: {
// Fill in the callback url you'd like to use for auth0
authCallback: '/auth/callback',
},
});
Set up your route handlers and actions:
// e.g. routes/auth/callback/+server.ts
export { handleAuthCallback as GET } from '$lib/server/auth';
// e.g. routes/auth/signin/+server.ts
export { handleSignInRedirect as GET } from '$lib/server/auth';
// e.g. routes/+page.server.ts
import { handleSignOut } from '$lib/server/auth';
export const actions = { logout: handleSignOut };
/// hooks.server.ts
import { authenticationHook } from '$lib/server/auth';
export const handle = authenticationHook;
/// app.d.ts
/// <reference types="lucia" />
declare global {
namespace App {
interface Locals {
auth: import('lucia').AuthRequest;
}
}
namespace Lucia {
type Auth = import('$lib/server/auth').Auth;
type DatabaseUserAttributes = {
// Define any extra user attributes here
};
type DatabaseSessionAttributes = {
access_token: string;
id_token: string;
};
}
}
export {};
Access the session from your server load functions
export async function load({ locals }) {
// Session is null if the user isn't logged in
const session = await locals.auth.validate();
// Note: depending on what you choose to expose on your session, you may want to pick and choose which data to pass down the the client to avoid leaking information.
return { session };
}
Add sign in / sign out buttons
To sign in, simply direct the user to your chosen sign in route:
<a href="/auth/signin">Sign in</a>
To sign out, create a form that posts to your logout action
<form method="post" action="/?/logout" use:enhance>
<button>Sign out</button>
</form>
Considerations
Why not Auth.js?
Auth.js was originally chosen as the authentication solution for our SvelteKit apps. Over time we realised many shortcomings of Auth.js and found ourselves battling with the lack of flexibilty it provided.
A few of the issues we ran in to:
- Lack of federated logout (logging out of auth0 after logging out of the app), with friction around adding it manually
- Need to manually update fields in the database when a user logs in (e.g. access token, profile data etc.)
- No cosmos database adapter, we had to create our own.
- Sign in/out cannot be done (by default) in a server side friendly way, as the APIs provided assume a browser environment. You're on your own to implement this server side.
- Auth.js assumes/expects that only one user exists per email. It either straight up denies it, or you can allow account linking by email but this no the desired behaviour. We need to, for example, support the case where a user has two distinct auth0 accounts with the same email and allow this to map to two distict users on the app side. (This was the final nail in the coffin.)
Why Lucia?
Lucia takes a slightly different approach to Auth.js' all inclusive apprach. Lucia provides JS APIs to create your auth implementation. This is more work than Auth.js, but it allows authentication to be tailored to our specific needs very naturally (read: without a mountain of hacks).
The downside is this would mean we would need to reimplement the handlers in every project, and this is where this package comes in. In a way it could be thought of as our very own custom Auth.js. It effectively adds back the opinion to Lucia, but this time it is our opinion!
2 days ago
26 days ago
26 days ago
26 days ago
1 month ago
1 month ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago