OAuth2

Google + GitHub sign-in.

8 minmedium

OAuth2 means "sign in with Google / GitHub" — the user clicks a button, gets redirected to the provider, comes back authenticated. Grit ships handlers for both. This lesson covers the wiring; the provider setup is a one-time copy-paste.

The flow

User → Click "Sign in with Google"
→ GET /api/auth/google
→ Grit redirects to accounts.google.com
→ Google authenticates user
→ Google redirects to /api/auth/google/callback?code=...
→ Grit exchanges code for user info
→ Grit creates or finds the user
→ Grit issues JWT (access + refresh tokens)
→ Frontend stores tokens, user is signed in

The 3-line provider setup

Get OAuth credentials from the provider once. For Google: create a project in Google Cloud Console, configure the consent screen, get client ID + secret. For GitHub: Developer Settings → OAuth Apps → New OAuth App.

Then add to .env:

.env
# Google OAuth
GOOGLE_CLIENT_ID=1234-...apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-...
# GitHub OAuth
GITHUB_CLIENT_ID=Iv1....
GITHUB_CLIENT_SECRET=...
# Where to redirect after success (your web frontend)
OAUTH_FRONTEND_URL=http://localhost:3001

That's it. Restart the API. Both providers are wired.

The callback URLs you give the provider

  • Google: http://localhost:8080/api/auth/google/callback
  • GitHub: http://localhost:8080/api/auth/github/callback

For production, swap localhost:8080 for your real domain. Each provider lets you list multiple callback URLs — typically you'd list both your localhost dev URL and your production URL.

State parameter: Grit's OAuth handler uses a CSRF-resistant state token signed with JWT_SECRET. The callback verifies the state matches, preventing CSRF attacks during the OAuth handshake.

Account linking — what happens if email already exists?

User signs up with alex@example.com + password. Later, they hit "Sign in with Google" and Google says "this account is alex@example.com". Grit's default: match the existing user, link the Google ID, sign them in. The user has both a password AND a Google login from then on.

If you want strict separation, edit authService.HandleOAuthCallback to reject the merge or prompt the user.

The user's profile data

What you get back from each provider:

Google: email, email_verified, name, picture, locale
GitHub: email (sometimes private!), login (username), name, avatar_url

GitHub's "email" may be private. Grit's callback handler grabs the primary verified email from GET /user/emails if the primary endpoint hides it.

Quick check

A user signs up with email/password, then later clicks 'Sign in with Google'. What's the default behaviour?

Try it

Wire one OAuth provider (Google or GitHub — your pick) on your bench-api. Then trigger the flow:

  1. Open http://localhost:8080/api/auth/google in your browser
  2. Sign in with your Google account
  3. You should land on the OAUTH_FRONTEND_URL with tokens in the query string
  4. Use that access token to hit /api/auth/me with curl

Paste the /me response in notes.md.

What's next

Password and OAuth handle the "something you know" factor. Next lesson — TOTP 2FA for the "something you have" factor.

Spot a typo? Have an idea?

Help us improve this lesson. One click opens a GitHub issue with the lesson URL pre-filled — suggest clearer wording, report a bug, or request more depth. The course keeps improving thanks to learners like you.

Suggest an improvement on GitHub