Skip to content

Commit f9659d6

Browse files
add sveltekit github oauth tutorial
1 parent 52b2d5f commit f9659d6

File tree

3 files changed

+228
-2
lines changed

3 files changed

+228
-2
lines changed

pages/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ We came to the conclusion that at least for the core of auth - sessions - it's b
3030

3131
All example code in this site is licensed under the [Zero-Clause BSD license](https://github.com/lucia-auth/next/blob/main/LICENSE-0BSD). You're free to use, copy, modify, and distribute it without any attribution. The license is approved by [the Open Source Initiative (OSI)](https://opensource.org/license/0bsd) and [Google](https://opensource.google/documentation/reference/patching#forbidden).
3232

33-
Everything else including the documentation text is licensed under the [MIT license](https://github.com/lucia-auth/next/blob/main/LICENSE-MIT).
33+
Everything else including the documentation text is licensed under the [MIT license](https://github.com/lucia-auth/next/blob/main/LICENSE-MIT).

pages/tutorials/github-oauth/astro.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Initialize the GitHub provider with the client ID and secret.
4646

4747
```ts
4848
import { GitHub } from "arctic";
49+
import { GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } from "$env/static/private";
4950

5051
export const github = new GitHub(import.meta.env.GITHUB_CLIENT_ID, import.meta.env.GITHUB_CLIENT_SECRET, null);
5152
```
@@ -166,7 +167,7 @@ if (Astro.locals.user === null) {
166167
return Astro.redirect("/login");
167168
}
168169

169-
const username = Astro.locals.user.username;
170+
const user = Astro.locals.user;
170171
```
171172

172173
## Sign out
+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
---
2+
title: "Tutorial: GitHub OAuth in SvelteKit"
3+
---
4+
5+
# Tutorial: GitHub OAuth in SvelteKit
6+
7+
_Before starting, make sure you've created the session and cookie API outlined in the [Sessions](/sessions/overview) page._
8+
9+
An [example project](https://github.com/lucia-auth/example-sveltekit-github-oauth) based on this tutorial is also available. You can clone the example locally or [open it in StackBlitz](https://stackblitz.com/github/lucia-auth/example-sveltekit-github-oauth).
10+
11+
```
12+
git clone [email protected]:lucia-auth/example-sveltekit-github-oauth.git
13+
```
14+
15+
## Create an OAuth App
16+
17+
[Create a GitHub OAuth app](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app). Set the redirect URI to `http://localhost:5173/login/github/callback`. Copy and paste the client ID and secret to your `.env` file.
18+
19+
```bash
20+
# .env
21+
GITHUB_CLIENT_ID=""
22+
GITHUB_CLIENT_SECRET=""
23+
```
24+
25+
## Update database
26+
27+
Update your user model to include the user's GitHub ID and username.
28+
29+
```ts
30+
interface User {
31+
id: number;
32+
githubId: number;
33+
username: string;
34+
}
35+
```
36+
37+
## Setup Arctic
38+
39+
We recommend using [Arctic](https://arcticjs.dev) for implementing OAuth. Arctic is a lightweight OAuth client library that supports 50+ providers out of the box.
40+
41+
```
42+
npm install arctic
43+
```
44+
45+
Initialize the GitHub provider with the client ID and secret.
46+
47+
```ts
48+
import { GitHub } from "arctic";
49+
50+
export const github = new GitHub(import.meta.env.GITHUB_CLIENT_ID, import.meta.env.GITHUB_CLIENT_SECRET, null);
51+
```
52+
53+
## Sign in page
54+
55+
Create `routes/login/+page.svelte` and add a basic sign in button, which should be a link to `/login/github`.
56+
57+
```svelte
58+
<!-- routes/login/+page.svelte -->
59+
<h1>Sign in</h1>
60+
<a href="/login/github">Sign in with GitHub</a>
61+
```
62+
63+
## Create authorization URL
64+
65+
Create an API route in `routes/login/github/+server.ts`. Generate a new state and create a new authorization URL. Store the state and redirect the user to the authorization URL. The user will be redirected to GitHub's sign in page.
66+
67+
```ts
68+
// routes/login/github/+server.ts
69+
import { redirect } from "@sveltejs/kit";
70+
import { generateState } from "arctic";
71+
import { github } from "$lib/server/oauth";
72+
73+
import type { RequestEvent } from "@sveltejs/kit";
74+
75+
export async function GET(event: RequestEvent): Promise<Response> {
76+
const state = generateState();
77+
const url = await github.createAuthorizationURL(state);
78+
79+
event.cookies.set("github_oauth_state", state, {
80+
path: "/",
81+
secure: import.meta.env.PROD,
82+
httpOnly: true,
83+
maxAge: 60 * 10,
84+
sameSite: "lax"
85+
});
86+
87+
redirect(302, url.toString());
88+
}
89+
```
90+
91+
## Validate callback
92+
93+
Create an API route in `routes/login/github/callback/+server.ts` to handle the callback. Check that the state in the URL matches the one that's stored. Then, validate the authorization code and stored code verifier. Use the access token to get the user's profile with the GitHub API. Check if the user is already registered; if not, create a new user. Finally, create a new session and set the session cookie to complete the authentication process.
94+
95+
```ts
96+
// routes/login/github/callback/+server.ts
97+
import { generateSessionToken, createSession, setSessionTokenCookie } from "$lib/server/session";
98+
import { github } from "$lib/server/oauth";
99+
import { OAuth2RequestError } from "arctic";
100+
101+
import type { RequestEvent } from "@sveltejs/kit";
102+
import type { OAuth2Tokens } from "arctic";
103+
104+
export async function GET(event: RequestEvent): Promise<Response> {
105+
const code = event.url.searchParams.get("code");
106+
const state = event.url.searchParams.get("state");
107+
const storedState = event.cookies.get("github_oauth_state") ?? null;
108+
if (code === null || state === null || storedState === null) {
109+
return new Response(null, {
110+
status: 400
111+
});
112+
}
113+
if (state !== storedState) {
114+
return new Response(null, {
115+
status: 400
116+
});
117+
}
118+
119+
let tokens: OAuth2Tokens;
120+
try {
121+
tokens = await github.validateAuthorizationCode(code);
122+
} catch (e) {
123+
// Invalid code or client credentials
124+
return new Response(null, {
125+
status: 400
126+
});
127+
}
128+
const githubUserResponse = await fetch("https://api.github.com/user", {
129+
headers: {
130+
Authorization: `Bearer ${tokens.accessToken()}`
131+
}
132+
});
133+
const githubUser = await githubUserResponse.json();
134+
const githubUserId = githubUser.id;
135+
const githubUsername = githubUser.login;
136+
137+
// TODO: Replace this with your own DB query.
138+
const existingUser = await getUserFromGitHubId(githubUserId);
139+
140+
if (existingUser) {
141+
const sessionToken = generateSessionToken();
142+
const session = await createSession(sessionToken, user.id);
143+
setSessionTokenCookie(event, token, session.expiresAt);
144+
return new Response(null, {
145+
status: 302,
146+
headers: {
147+
Location: "/"
148+
}
149+
});
150+
}
151+
152+
// TODO: Replace this with your own DB query.
153+
const user = await createUser(githubUserId, githubUsername);
154+
155+
const sessionToken = generateSessionToken();
156+
const session = await createSession(sessionToken, user.id);
157+
setSessionTokenCookie(event, token, session.expiresAt);
158+
159+
return new Response(null, {
160+
status: 302,
161+
headers: {
162+
Location: "/"
163+
}
164+
});
165+
}
166+
```
167+
168+
## Get the current user
169+
170+
If you implemented the middleware outlined in the [Session cookies in SvelteKit](/sessions/cookies/sveltekit) page, you can get the current session and user from `Locals`.
171+
172+
```ts
173+
// routes/+page.server.ts
174+
import { redirect } from "@sveltejs/kit";
175+
176+
import type { PageServerLoad } from "./$types";
177+
178+
export const load: PageServerLoad = async (event) => {
179+
if (!event.locals.user) {
180+
return redirect(302, "/login");
181+
}
182+
183+
return {
184+
user
185+
};
186+
};
187+
```
188+
189+
## Sign out
190+
191+
Sign out users by invalidating their session. Make sure to remove the session cookie as well.
192+
193+
```ts
194+
// routes/+page.server.ts
195+
import { fail, redirect } from "@sveltejs/kit";
196+
import { invalidateSession, deleteSessionTokenCookie } from "$lib/server/session";
197+
198+
import type { Actions, PageServerLoad } from "./$types";
199+
200+
export const load: PageServerLoad = async ({ locals }) => {
201+
// ...
202+
};
203+
204+
export const actions: Actions = {
205+
default: async (event) => {
206+
if (event.locals.session === null) {
207+
return fail(401);
208+
}
209+
await invalidateSession(event.locals.session.id);
210+
deleteSessionTokenCookie(event);
211+
return redirect(302, "/login");
212+
}
213+
};
214+
```
215+
216+
```svelte
217+
<!-- routes/+page.svelte -->
218+
<script lang="ts">
219+
import { enhance } from "$app/forms";
220+
</script>
221+
222+
<form method="post" use:enhance>
223+
<button>Sign out</button>
224+
</form>
225+
```

0 commit comments

Comments
 (0)