GitHat Next.js Quickstart: Sign-in in Minutes
Accurate, source-verified guide to installing @githat/nextjs, wrapping the app in GitHatProvider, mounting the sign-in form and proxy route, and reading the session server-side.
Install
The SDK package is @githat/nextjs. It requires Next.js >=14, React >=18, and Node >=18.
npm install @githat/nextjsEnvironment variables
You need two server-side env vars. Neither should carry the NEXT_PUBLIC_ prefix — the publishable key is intentionally not baked into the client bundle so it can be rotated without a rebuild.
GITHAT_PUBLISHABLE_KEY — your app's publishable key (pk_live_… format, verified in the real source). Required by the provider and the API proxy.
GITHAT_APP_ID — your app's GitHat-Apps record id (UUID). Required for audience enforcement on access tokens and for app-scoped APIs. Optional but strongly recommended for production.
# .env.local (never commit)
GITHAT_PUBLISHABLE_KEY=pk_live_...
GITHAT_APP_ID=<your-app-uuid>Mount the same-origin API proxy
The SDK talks to https://api.githat.io. To keep auth cookies on your own domain and avoid cross-origin cookie restrictions, mount a catch-all proxy route at src/app/api/githat/[...path]/route.ts. The SDK exports githatApiProxy() from @githat/nextjs/server for this purpose.
Then point the provider's apiUrl at /api/githat (see next step). This is the pattern used in production — every SDK fetch goes same-origin, the proxy forwards it to api.githat.io, and Set-Cookie headers land on your domain.
// src/app/api/githat/[...path]/route.ts
import { githatApiProxy } from '@githat/nextjs/server';
export const dynamic = 'force-dynamic';
export const revalidate = 0;
export const { GET, POST, PUT, PATCH, DELETE, OPTIONS } = githatApiProxy();Wrap the app in GitHatProvider
GitHatProvider takes a config object of type GitHatConfig. The required field is publishableKey. The server reads GITHAT_PUBLISHABLE_KEY at runtime and passes it into the provider as a prop — this keeps the key out of the compiled client bundle.
tokenStorage: 'cookie' stores the access and refresh tokens in httpOnly cookies via the same-origin proxy. This is required for SSR and for getAuth()/auth() to work server-side. The default is 'localStorage', which is only appropriate for fully static deployments.
apiUrl: '/api/githat' routes all SDK traffic through the proxy route mounted above.
appName is shown in the form heading ('Sign in to YourApp'). When omitted, the form shows a generic 'Sign in' with no app branding.
// src/app/layout.tsx (Server Component)
import { GitHatProvider } from '@githat/nextjs';
import type { GitHatConfig } from '@githat/nextjs';
const githatConfig: GitHatConfig = {
publishableKey: process.env.GITHAT_PUBLISHABLE_KEY!,
appId: process.env.GITHAT_APP_ID, // optional but recommended
apiUrl: '/api/githat', // routes through your proxy
appName: 'YourApp', // shown in the sign-in form heading
tokenStorage: 'cookie', // httpOnly cookies; required for SSR
afterSignInUrl: '/dashboard',
afterSignOutUrl: '/',
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<GitHatProvider config={githatConfig}>
{children}
</GitHatProvider>
</body>
</html>
);
}Mount the sign-in form
SignInForm is a client component exported from @githat/nextjs. Drop it into any 'use client' page. It renders email/password fields, a magic-link toggle, and OAuth buttons (Google, Apple, Microsoft, Facebook, Instagram, TikTok by default; GitHub is opt-in).
The oauth prop controls which social buttons appear. Pass oauth={[]} to hide them all. The showMagicLink prop (default true) shows a 'Use a magic link instead' toggle — only set it to false if you have not yet mounted the /sign-in/magic landing route (see below), otherwise the emailed link lands on a 404.
onSuccess fires after a successful credential sign-in. OAuth redirects handle their own navigation via afterSignInUrl on the provider.
// src/app/(auth)/sign-in/page.tsx
'use client';
import { SignInForm } from '@githat/nextjs';
import { useRouter } from 'next/navigation';
export default function SignInPage() {
const router = useRouter();
return (
<SignInForm
onSuccess={() => router.push('/dashboard')}
signUpUrl="/sign-up"
forgotPasswordUrl="/forgot-password"
oauth={['google', 'github']} // or omit for all six defaults
showMagicLink={true} // requires /sign-in/magic route below
footer="githat"
/>
);
}Magic-link landing route (required when showMagicLink is true)
When the user requests a magic link, the backend emails a URL pointing at <your-origin>/sign-in/magic?token=…. You must mount MagicLinkVerify on that path — without it every magic-link email lands on a 404.
MagicLinkVerify uses useSearchParams() and must be wrapped in Suspense.
// src/app/(auth)/sign-in/magic/page.tsx
'use client';
import { Suspense } from 'react';
import { MagicLinkVerify } from '@githat/nextjs';
function MagicLinkContent() {
return (
<MagicLinkVerify
afterSignInUrl="/dashboard"
signInUrl="/sign-in"
/>
);
}
export default function MagicLinkPage() {
return (
<Suspense fallback={<p>Verifying…</p>}>
<MagicLinkContent />
</Suspense>
);
}OAuth callback routes
Each social provider needs a callback page at /auth/{provider}/callback. Import the matching Callback component from @githat/nextjs. Available exports: GoogleCallback, GitHubCallback, MicrosoftCallback, FacebookCallback, InstagramCallback, TikTokCallback.
Apple is a special case: Apple uses response_mode=form_post, so the callback is a POST, not a GET. You need a Route Handler at /auth/apple/callback/route.ts that reads the form body and redirects to a page component that mounts AppleCallback. See the AppleCallback JSDoc in the SDK for the full pattern.
// src/app/auth/google/callback/page.tsx
'use client';
import { GoogleCallback } from '@githat/nextjs';
export default function GoogleCallbackPage() {
return (
<GoogleCallback
redirectUrl="/dashboard"
newUserRedirectUrl="/onboard"
/>
);
}Reading the session server-side
Import from @githat/nextjs/server. Three helpers are available:
auth() — for Server Components, layouts, and server actions. Returns AuthPayload | null. Reads from the githat_access cookie (or Authorization: Bearer header). No Request argument needed.
getAuth(request) — for API Route handlers where you have a Request object.
currentUser(options?) — convenience wrapper over auth() plus a /auth/me fetch. Returns { id, email, orgId, orgRole, raw } | null, cached within one Server Component render via React cache().
The AuthPayload shape includes userId, email, orgId, orgSlug, role ('owner' | 'admin' | 'member' | null), tier, aud, and appId.
withAuth(handler, options) wraps an API Route handler and injects auth as a second argument, returning 401 JSON automatically on missing/invalid tokens.
// Server Component
import { auth } from '@githat/nextjs/server';
import { redirect } from 'next/navigation';
export default async function Dashboard() {
const session = await auth();
if (!session) redirect('/sign-in');
return <h1>Hello {session.email}</h1>;
}
// API Route with withAuth
import { withAuth } from '@githat/nextjs/server';
export const GET = withAuth(async (request, session) => {
// session.userId, session.orgId, session.role are available
return Response.json({ userId: session.userId });
}, {
audience: process.env.GITHAT_APP_ID,
});Reading auth state client-side
useAuth() returns the full GitHatContextValue. The most useful fields for conditional rendering are isSignedIn, isLoading, and user (a GitHatUser with id, email, name, avatarUrl, emailVerified, and linked provider ids).
signIn(email, password) and signOut() are also on the returned object. signIn returns void on success or { requiresMfa: true, mfaTicket: string } when the account has TOTP enabled — in that case render MfaChallenge with the ticket.
'use client';
import { useAuth } from '@githat/nextjs';
export function Header() {
const { isSignedIn, isLoading, user, signOut } = useAuth();
if (isLoading) return null;
if (!isSignedIn) return <a href="/sign-in">Sign in</a>;
return (
<div>
<span>{user?.email}</span>
<button onClick={() => signOut()}>Sign out</button>
</div>
);
}Where the session token comes from
When tokenStorage is 'cookie' (recommended), a successful login causes the backend to emit two httpOnly cookies: githat_access (15-minute access token, RS256 JWT) and githat_refresh (7-day refresh token). In production these are prefixed __Host-githat_access and __Host-githat_refresh, which forces Secure + Path=/ and no Domain= attribute.
The SDK proxy route at /api/githat reads the access cookie, forwards it as Authorization: Bearer <jwt> to api.githat.io (because Node's fetch blocks the Cookie header on cross-origin requests), and re-emits any Set-Cookie on the response so the token lands on your domain.
auth() / getAuth() on the server read the githat_access cookie from next/headers via the JWKS endpoint at https://api.githat.io/.well-known/jwks.json. The public key is cached for 5 minutes per Lambda container — token verification is pure crypto on the hot path.
When tokenStorage is 'localStorage', tokens are stored in the browser and readable by any script on the page. Only use this for fully static hosts where httpOnly cookies are not possible.
Auth endpoints (from the backend router)
These are the real routes registered in MicroBackEnds/src/handlers/auth/router.js. The SDK calls them through your /api/githat proxy. You never need to call them directly unless you are building a custom flow.
POST /auth/login — email + password sign-in. Returns tokens in cookie (tokenStorage: 'cookie') or JSON body (localStorage mode). Returns { requiresMfa, mfaTicket } when TOTP is enabled.
POST /auth/register — create a new account.
POST /auth/logout — revoke the session.
POST /auth/refresh — rotate access + refresh tokens.
GET /auth/me — return the current user's GitHatUser profile.
POST /auth/magic-link/request — send a magic link email.
POST /auth/magic-link/verify — exchange the one-time token for session tokens.
POST /auth/2fa/login-verify — verify a TOTP code for an MFA-gated sign-in.
GET /oauth/authorize and POST /oauth/token implement the RFC 6749 authorization-code flow used by the 'Continue with GitHat' cross-app button.
GET /auth/oauth/{github,google,microsoft,facebook,apple,instagram,tiktok}/url and POST /auth/oauth/{provider} handle social provider redirects and callback exchanges.
Every request to these endpoints via the proxy must include the X-GitHat-App-Key header set to your publishable key. The proxy route adds this automatically from GITHAT_PUBLISHABLE_KEY.
next.config helper (CSP)
The SDK calls https://api.githat.io from the browser. Without connect-src https://api.githat.io in your Content-Security-Policy, every SDK fetch is blocked. withGitHat() from @githat/nextjs/server merges the required CSP and security headers into your next.config automatically.
// next.config.ts
import { withGitHat } from '@githat/nextjs/server';
const nextConfig = {};
export default withGitHat(nextConfig);