Discord oAuth with Sveltekit
How to log in to a website with discord
Work in progress!
Based on the amazing Sveltesnaps example
Setup
Discord App
Create a discord app on The Discord Developer Portal
Environment variables
Specify the following environment variables
- DISCORD_CLIENT_ID
- DISCORD_CLIENT_SECRET
Login and logout actions
/src/routes/auth/+page.server.ts
import { redirect } from '@sveltejs/kit';
import { DISCORD_CLIENT_ID } from '$env/static/private';
export const actions = {
login: async ({ cookies, url }) => {
const auth = new URL('https://discord.com/oauth2/authorize');
const state = crypto.randomUUID();
cookies.set('discord_state', state, { path: '/' });
auth.searchParams.set('response_type', 'code');
auth.searchParams.set('client_id', DISCORD_CLIENT_ID);
auth.searchParams.set('scope', 'identify');
auth.searchParams.set('prompt', 'none');
auth.searchParams.set('redirect_uri', `${url.origin}/auth/callback`);
auth.searchParams.set('state', state);
throw redirect(303, auth.href);
},
logout: async ({ cookies, locals }) => {
const session_id = cookies.get('session');
if (session_id) {
// delete session id from database here
cookies.delete('session', { path: '/' });
}
locals.user = undefined;
}
};
To log in use the following code on any page:
<form method="POST" action="/auth?/login" use:enhance>
<button class="text-pink-600">log in</button>
</form>
<form method="POST" action="/auth?/logout" use:enhance>
<button>log out</button>
</form>
Auth callback route
(thats the url you put as the redirect url in the discord dashboard)
src/routes/auth/callback/+server.ts
import { DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET } from '$env/static/private';
import { error, redirect } from '@sveltejs/kit';
export async function GET({ url, cookies }) {
const state = url.searchParams.get('state');
if (state !== cookies.get('discord_state')) {
throw error(403);
}
const code = url.searchParams.get('code');
if (!code) {
throw new Error('callback URL was called without a code');
}
const { access_token } = await get_token(code, url.origin);
// more info about the tokens: https://discord.com/developers/docs/topics/oauth2#authorization-code-grant-access-token-response
const user = await get_user(access_token);
const avatar = user.avatar
? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.webp`
: null;
// create/update user in database (discord id, name, avatar, etc)
// create session in database (session id, discord account id)
const session = //session id from database
cookies.set('session', session, { path: '/' });
throw redirect(307, '/');
}
async function get_token(code: string, origin: string) {
const data = new URLSearchParams({
code,
grant_type: 'authorization_code',
redirect_uri: `${origin}/auth/callback`,
client_id: DISCORD_CLIENT_ID,
client_secret: DISCORD_CLIENT_SECRET
});
const response = await fetch('https://discord.com/api/oauth2/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: data.toString()
});
const json = await response.json();
if (json.error) {
throw new Error(json.error_description);
}
return json;
}
async function get_user(access_token: string) {
const response = await fetch('https://discord.com/api/users/@me', {
headers: {
Authorization: `Bearer ${access_token}`
}
});
const user = await response.json();
return user;
}
Hooks
src/hooks.server.ts
export async function handle({ event, resolve }) {
const session_id = event.cookies.get('session');
if (session_id) {
const account = // check if session token is valid and return user
event.locals.user = account
}
return resolve(event);
}