Skip to content

Instantly share code, notes, and snippets.

@claughinghouse
Created November 28, 2023 19:32
Show Gist options
  • Save claughinghouse/86bcc219368f946fbdc8a3b69e459b66 to your computer and use it in GitHub Desktop.
Save claughinghouse/86bcc219368f946fbdc8a3b69e459b66 to your computer and use it in GitHub Desktop.
SvelteKit and SST Auth
# This file is in the sveltekit parent directory and is a seperate stack for SST.
import { Account } from "@rebut/core/account";
import { Config } from "sst/node/config";
import { AuthHandler, GoogleAdapter } from "sst/node/future/auth";
import { sessions } from "./sessions";
export const handler = AuthHandler({
sessions,
providers: {
google: GoogleAdapter({
mode: "oidc",
clientID: Config.GOOGLE_CLIENT_ID,
}),
},
callbacks: {
auth: {
async allowClient(clientID, redirect) {
return true;
},
async success(input, response) {
const claims = input.tokenset.claims();
const email = claims.email;
if (!claims.email) throw new Error("No email found");
let accountID = await Account.fromEmail(claims.email).then(
(x) => x?.accountID
);
if (!accountID) {
// console.log("No account found, creating new account:", claims.email);
const createAccount = await Account.create({
email: claims.email,
emailVerified: claims.email_verified!,
avatar: claims.picture?.replace("s96-c", "s400-c") || "",
name: claims.name!,
givenName: claims.given_name,
familyName: claims.family_name,
});
// console.log("Account created:", accountID);
accountID = createAccount.accountID;
}
return response.session({
type: "account",
properties: {
accountID: accountID!,
},
});
},
},
},
});
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
interface Locals {
account: Account | null;
}
// interface PageData {}
// interface Platform {}
}
}
export {};
import { checkAuth } from '$lib/server/auth';
import { redirect, type Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
// validate the user on any request
event.locals.account = checkAuth(event);
if (event.url.pathname.startsWith('/dashboard')) {
if (!event.locals.account) {
throw redirect(302, '/');
}
}
const response = await resolve(event);
return response;
};
import type { RequestEvent } from '@sveltejs/kit';
import { Session } from 'sst/node/future/auth';
export const checkAuth = (event: RequestEvent) => {
const { cookies } = event;
const accountToken = cookies.get('auth_token');
if (accountToken) {
const session = Session.verify(accountToken);
// If the auth_token cookie is present but its not valid, eat the cookie.
if (session.type === 'public') {
// console.log('invalid session');
cookies.set('auth_token', '', {
path: '/',
expires: new Date(0)
});
}
// console.log('session in Auth lib', session);
return session;
}
return null;
};
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ locals }) => {
// console.log('locals', locals);
return {
account: locals.account
};
};
<script lang="ts">
import { page } from '$app/stores';
import { PUBLIC_VITE_AUTH_URL } from '$env/static/public';
import type { PageData } from './$types';
export let data: PageData;
const params = new URLSearchParams({
client_id: 'sk',
redirect_uri: $page.url.origin + '/auth/callback',
response_type: 'code',
provider: 'google'
});
let loginUrl = `${PUBLIC_VITE_AUTH_URL}` + '/authorize?' + params.toString();
</script>
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
{#if data.account}
<p>Go to the <a href="/dashboard">dashboard</a></p>
<form action="/auth/logout" method="POST">
<button type="submit">Log out</button>
</form>
{:else}
<p>Not logged in</p>
<button id="home_sk3">
<a href={loginUrl}>Login with Google</a>
</button>
{/if}
import { PUBLIC_VITE_AUTH_URL } from '$env/static/public';
import { error } from '@sveltejs/kit';
export async function GET(evt) {
const code = evt.url.searchParams.get('code');
if (code === null || code.trim() === '') {
throw error(400, {
message: 'Missing code. <a href="/">Try again</a>'
});
}
const response = await fetch(`${PUBLIC_VITE_AUTH_URL}` + '/token', {
method: 'POST',
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: 'sk',
code,
redirect_uri: `${evt.url.origin}${evt.url.pathname}`
})
}).then((r) => r.json());
evt.cookies.set('auth_token', response.access_token, {
path: '/',
sameSite: 'lax',
httpOnly: true,
maxAge: 60 * 60 * 24, // 60 seconds * 60 minutes * 24 = 1 day
secure: import.meta.env.NODE_ENV === 'production'
});
return new Response('Redirect', {
status: 302,
headers: { Location: `${evt.url.origin}/dashboard` }
});
}
import { redirect } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';
export const load: PageServerLoad = async () => {
// we only use this endpoint for the api
// and don't need to see the page
throw redirect(302, '/');
};
export const actions: Actions = {
default({ cookies }) {
// eat the cookie
cookies.set('auth_token', '', {
path: '/',
expires: new Date(0)
});
// redirect the user
throw redirect(302, '/');
}
};
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ locals }) => {
if (locals.account.type !== 'account') {
throw redirect(302, '/');
}
return {
account: locals.account
};
};
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
// console.log('dashboard route', data);
</script>
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
<p>Logged in with accountID {data.account.properties.accountID}</p>
<p>Go to the <a href="/">home</a></p>
<form action="/auth/logout" method="POST">
<button type="submit">Log out</button>
</form>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment