google apple github auth for nextjs

Add Google, GitHub, and Apple Sign-In to a Next.js App

We may earn an affiliate commission through purchases made from our guides and tutorials.

Modern SaaS users expect a one-click sign-in. The fastest way to deliver it in Next.js is with Auth.js (NextAuth v5), which ships tight App Router integration and first-party providers for Google, GitHub, and Apple. We’ll stand up a Next.js 15 app, wire providers with environment-first config, add secure buttons that work with Server Actions, and get you ready to deploy. You’ll need standard OAuth credentials for each provider and a place to host environment variables; if you deploy on DigitalOcean App Platform, make sure you have a DigitalOcean account so you can set those variables cleanly during the final step.

You will create a Next.js 15 app that exposes /api/auth/[...nextauth] via Route Handlers, configures Google, GitHub, and Apple providers using environment variables only, protects pages with middleware.ts, and renders sign-in/out buttons that run on the server (no client bundles). This approach uses Auth.js v5’s handler re-exports, environment variable inference (AUTH_*), and server helpers (auth, signIn, signOut).

1) Create and prepare the Next.js project

Install Next.js 15 (or later). If you’re upgrading, run the codemod; for new apps, scaffold from scratch.

# New app
npx create-next-app@latest next-auth-providers --use-npm

cd next-auth-providers

# Install Auth.js (NextAuth v5, currently published under the beta tag)
npm install next-auth@beta

Next.js 15 is stable and works with the App Router; Auth.js v5 documents installation with next-auth@beta.

Generate a cryptographically strong secret for token/cookie encryption:

# Creates a 32+ char secret; copy it into .env.local as AUTH_SECRET
npx auth secret

Auth.js requires a secret in production and recommends the AUTH_ prefix for all variables.

2) Add the Auth.js core files (App Router)

Create auth.ts at the project root. This is the single source of truth for your auth config and server helpers.

// auth.ts
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import GitHub from "next-auth/providers/github"
import Apple from "next-auth/providers/apple"

// Minimal config: rely on AUTH_* env inference for all providers
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [Google, GitHub, Apple],
  // Optional: add callbacks, session strategy, events here
})

Auth.js v5 automatically reads AUTH_GOOGLE_ID/SECRET, AUTH_GITHUB_ID/SECRET, and AUTH_APPLE_ID/SECRET if present—so you don’t need to pass clientId/clientSecret manually.

Expose the Route Handler used by the OAuth flows:

// app/api/auth/[...nextauth]/route.ts
export const runtime = "edge" // optional; remove if you prefer Node runtime
export { GET, POST } from "../../../../auth"

Auth.js provides handlers that you re-export as GET and POST.

Keep users signed in and gate routes with Middleware:

// middleware.ts
export { auth as middleware } from "./auth"

// Optionally restrict to specific paths with a matcher:
// export const config = { matcher: ["/dashboard/:path*"] }

auth is designed to run in Middleware to keep sessions fresh and to protect routes.

3) Configure environment variables

Create .env.local (not committed to VCS):

# Required for encryption (production only strictly required)
AUTH_SECRET=copy-from-npx-auth-secret

# Set this to your full site URL in production
# (AUTH_URL is aliased to NEXTAUTH_URL; either works)
AUTH_URL=http://localhost:3000

# Google OAuth
AUTH_GOOGLE_ID=...
AUTH_GOOGLE_SECRET=...

# GitHub OAuth
AUTH_GITHUB_ID=...
AUTH_GITHUB_SECRET=...

# Apple OAuth
AUTH_APPLE_ID=...
AUTH_APPLE_SECRET=...

Auth.js v5 infers provider credentials from AUTH_{PROVIDER}_{ID|SECRET} and aliases AUTH_URL/AUTH_SECRET to their NextAuth v4 equivalents.

4) Create working UI: server-side sign-in/out

Add sign-in buttons that execute on the server with zero client JS:

// app/page.tsx
import { auth, signIn, signOut } from "../auth"

export default async function Home() {
  const session = await auth()
  return (
    <main style={{ display: "grid", gap: 12, padding: 24 }}>
      <h1>Next.js + Auth.js providers</h1>

      {!session && (
        <>
          <form action={async () => { "use server"; await signIn("google") }}>
            <button>Sign in with Google</button>
          </form>
          <form action={async () => { "use server"; await signIn("github") }}>
            <button>Sign in with GitHub</button>
          </form>
          <form action={async () => { "use server"; await signIn("apple") }}>
            <button>Sign in with Apple</button>
          </form>
        </>
      )}

      {session && (
        <>
          <p>Signed in as {session.user?.email}</p>
          <form action={async () => { "use server"; await signOut() }}>
            <button>Sign out</button>
          </form>
        </>
      )}
    </main>
  )
}

auth() reads the session in a Server Component; signIn/signOut are Server Actions provided by Auth.js v5.

5) Register each OAuth app and set callback URLs

Auth.js shows canonical callback patterns and the exact env variable names. You’ll copy IDs/secrets from each provider’s dashboard into .env.local.

Google

Create an OAuth 2.0 Client ID in Google Cloud Console. Set the redirect URI to:

https://<your-domain>/api/auth/callback/google

Then set AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET.

GitHub

Create a GitHub OAuth App (not a GitHub App) and set the redirect to:

https://<your-domain>/api/auth/callback/github

Then set AUTH_GITHUB_ID and AUTH_GITHUB_SECRET. If you need access to private emails, grant the “Email addresses” permission when configuring the app.

Apple

Use an Apple Developer account. Create a Services ID (acts as client_id) and a private key (.p8) associated with your Team. Generate a client secret as a signed JWT (iss=TeamID, sub=ServicesID, aud=https://appleid.apple.com, 6-month max expiry). Set:

AUTH_APPLE_ID=<your Services ID>
AUTH_APPLE_SECRET=<your generated client secret JWT>

Use this redirect URI:

https://<your-domain>/api/auth/callback/apple

Plan to rotate the Apple client secret at least every 6 months; Apple rejects longer expirations.

6) Local run and basic verification

Start the app:

npm run dev

Visit http://localhost:3000. Click a provider button, complete the OAuth screen, and you should be redirected back with a populated session. If you see missing secret errors, verify AUTH_SECRET is set; if a provider complains about redirect mismatch, double-check the exact callback URL in that provider’s dashboard.

7) Protect routes or entire sections

To require auth for a subtree (for example /dashboard), add a matcher:

// middleware.ts
import { auth } from "./auth"

export default auth()

export const config = {
  matcher: ["/dashboard/:path*"],
}

Auth decisions are controlled in callbacks.authorized if you need role-based logic.

8) Deploy and set environment on DigitalOcean App Platform

When you push to a public repo, create an App Platform app from that repo. In Settings → Environment Variables, add the keys from .env.local to your app and redeploy. The platform injects variables into your runtime; you can confirm via the console if needed. Also set AUTH_URL to your production domain (for example, https://your-app.ondigitalocean.app).

9) Production hardening

First, ensure your production URL is correct and served over HTTPS; Auth.js will use secure cookies automatically on HTTPS origins. Second, scope provider permissions narrowly; request only the profile/email scopes you need. Third, for Apple, schedule rotation of the client secret before the 6-month ceiling, or generate the JWT dynamically during startup or on demand. Finally, keep Next.js current; 15.5 includes type-safety and Turbopack improvements that benefit auth workflows and local DX.

Reference snippets you’ll reuse later

Sign-in buttons inside any Server Component:

import { signIn } from "../auth"

export function GoogleButton() {
  return (
    <form action={async () => { "use server"; await signIn("google") }}>
      <button>Continue with Google</button>
    </form>
  )
}

Reading the user in a protected page:

// app/dashboard/page.tsx
import { auth } from "../../auth"
import { redirect } from "next/navigation"

export default async function Dashboard() {
  const session = await auth()
  if (!session) redirect("/")

  return <pre>{JSON.stringify(session.user, null, 2)}</pre>
}

Extending provider options (e.g., Google offline access):

// auth.ts (partial)
import Google from "next-auth/providers/google"

export const { handlers, auth } = NextAuth({
  providers: [
    Google({
      authorization: { params: { prompt: "consent", access_type: "offline" } },
    }),
  ],
})

The authorization.params pattern forces Google to issue a refresh token when needed.

Troubleshooting

If a provider rejects the redirect, copy the exact URL from the provider docs and paste it into the provider console. For missing envs in production, re-enter variables and trigger a redeploy so new values take effect. If sessions do not persist locally, remember that secure cookies default to off on http://localhost and on in HTTPS—moving to a real domain usually resolves cookie scope issues.

Wrap-up

You now have Google, GitHub, and Apple sign-in in a Next.js 15 app using Auth.js v5’s handler API, server helpers, and AUTH_* environment inference. From here, add an adapter (Prisma, MongoDB, Postgres) to persist user data, or keep the default JWT sessions for pure OAuth. When you deploy, set your variables in your hosting provider, point AUTH_URL at your production domain, and rotate the Apple client secret on schedule.

Was this helpful?

Thanks for your feedback!
Alex is the resident editor and oversees all of the guides published. His past work and experience include Colorlib, Stack Diary, Hostvix, and working with a number of editorial publications. He has been wrangling code and publishing his findings about it since the early 2000s.

Leave a comment

Your email address will not be published. Required fields are marked *