1---
2title: "Part 5: Add GitHub sign-in"
3description: Register a GitHub OAuth app, bind its credentials through Effect Config, and sign in from the browser.
4sidebar:
5 order: 5
6---
7
8Continue from [Part 4](/better-auth/tutorial/part-4). GitHub credentials come from a GitHub OAuth application; Alchemy's automatic signing secret is a separate value.
9
10## Register the local OAuth application
11
12Use the `DEV_URL` captured from Alchemy in [Part 1](/better-auth/tutorial/part-1). Print the two URLs to register:
13
14```sh
15printf 'Homepage URL: %s\n' "$DEV_URL"
16printf 'Authorization callback URL: %s/api/auth/callback/github\n' "$DEV_URL"
17```
18
19Open [GitHub's new OAuth application form](https://github.com/settings/applications/new), name the app, and paste those values into its homepage and callback fields.
20
21Use a separate OAuth application for production so its credentials remain independent of local development.
22
23## Keep credentials out of Git
24
25```gitignore
26# .gitignore
27.env
28.env.*
29!.env.example
30node_modules/
31.alchemy/
32public/ui.js
33```
34
35Preserve the local `.alchemy` directory even though it is ignored by Git; it contains this tutorial's deployment state.
36
37## Supply the GitHub credentials
38
39Generate a client secret in the GitHub application settings, then save the values locally:
40
41```dotenv
42# .env
43GITHUB_CLIENT_ID=your-local-oauth-client-id
44GITHUB_CLIENT_SECRET=your-local-oauth-client-secret
45```
46
47These names are application configuration keys, not built-in Better Auth environment variables. The next step reads them explicitly.
48
49## Read the client ID
50
51```diff lang="typescript"
52 // src/auth.ts
53+import * as Config from "effect/Config";
54
55-const makeAuth = BetterAuth({
56- basePath: "/api/auth",
57- emailAndPassword: { enabled: true },
58+const makeAuth = Effect.gen(function* () {
59+ const clientId = yield* Config.String("GITHUB_CLIENT_ID");
60+ return yield* BetterAuth({
61+ basePath: "/api/auth",
62+ emailAndPassword: { enabled: true },
63+ });
64 });
65```
66
67Resolve configuration while constructing the Auth layer so Alchemy discovers and binds it. Reading it only inside a request handler would be too late for deployment-time binding discovery.
68
69## Read the client secret
70
71```diff lang="typescript"
72 // src/auth.ts
73+import * as Redacted from "effect/Redacted";
74
75 const makeAuth = Effect.gen(function* () {
76 const clientId = yield* Config.String("GITHUB_CLIENT_ID");
77+ const clientSecret = yield* Config.Redacted("GITHUB_CLIENT_SECRET");
78 return yield* BetterAuth({
79 basePath: "/api/auth",
80 emailAndPassword: { enabled: true },
81 });
82 });
83```
84
85Keep the secret redacted until the library needs the string. Neither credential belongs in the browser bundle.
86
87## Enable GitHub
88
89```diff lang="typescript"
90 // src/auth.ts
91 return yield* BetterAuth({
92 basePath: "/api/auth",
93 emailAndPassword: { enabled: true },
94+ socialProviders: {
95+ github: {
96+ clientId,
97+ clientSecret: Redacted.value(clientSecret),
98+ },
99+ },
100 });
101```
102
103The existing Auth service and middleware infer the updated options. The database layer stays unchanged.
104
105## Add a sign-in button
106
107```diff lang="html"
108 <!-- public/index.html -->
109 <button id="sign-out" type="button">Sign out</button>
110+<button id="github" type="button">Sign in with GitHub</button>
111```
112
113## Start the authorization flow
114
115```typescript
116// Append to src/ui.ts
117const signInWithGitHub = async () => {
118 const { error } = await authClient.signIn.social({
119 provider: "github",
120 callbackURL: "/",
121 });
122 if (error) show(error.message ?? "GitHub sign-in failed");
123};
124
125document.querySelector("#github")!.addEventListener("click", () => {
126 void signInWithGitHub().catch(() => show("Unable to reach the server."));
127});
128```
129
130The client redirects to GitHub and returns through the registered callback. The resulting session works with the same `/api/me` endpoint.
131
132Better Auth handles failed callbacks on its built-in error page. No custom error query parameter or callback handler is needed.
133
134## Restart with the new configuration
135
136Stop the running development command, rebuild the client, and start it again:
137
138```sh
139bun build ./src/ui.ts --target browser --outdir ./public
140bunx alchemy dev --stage tutorial
141```
142
143Try GitHub sign-in and call the protected API. Then sign out, retry, and cancel at GitHub to check the error path.
144
145Use the [GitHub provider guide](/better-auth/sign-in-providers/github) for account linking and provider-specific settings. Continue to [Part 6: Deploy and verify](/better-auth/tutorial/part-6).