A centralized OAuth 2.0 Authorization Server that enables Single Sign-On (SSO) across multiple Nuxt Studio-powered websites.
Instead of configuring GitHub OAuth on each Nuxt Studio site individually, this server acts as a central identity provider. Users authenticate once with GitHub and gain access to all connected Nuxt Studio sites, with their GitHub token automatically passed through for Git operations.
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ Site A │ │ Nuxt Studio SSO │ │ Site B │
│ docs.example.com │ │ auth.example.com │ │ blog.example.com │
│ │ │ │ │ │
│ Client ID: abc123 │◄───►│ OAuth Server │◄───►│ Client ID: xyz789 │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
│
┌──────┴──────┐
│ Database │
│ (Users, │
│ Clients, │
│ Tokens) │
└─────────────┘
- Single Sign-On: Log in once, access all connected Nuxt Studio sites
- OpenID Connect: Standards-compliant OAuth 2.0 + OIDC implementation
- PKCE Required: S256 PKCE is mandatory for all authorization requests (OAuth 2.1)
- GitHub Authentication: Users log in with GitHub, enabling automatic token pass-through
- GitHub Token Pass-Through: Eliminates the need for a separate
STUDIO_GITHUB_TOKEN - Admin System: First user becomes admin, only admins can manage OAuth clients
- User Dashboard: Non-admin users can view all websites where they can sign in
- Preview URL Patterns: Support for Vercel, Netlify, and other preview deployment platforms
- Multi-Cloud: Deploy anywhere Nuxt runs — Vercel, Netlify, Cloudflare, AWS, and more via NuxtHub
| Component | Technology |
|---|---|
| Framework | Nuxt 4 |
| Database | SQLite (via NuxtHub) |
| ORM | Drizzle ORM |
| Auth | nuxt-auth-utils |
| Token Format | JWT with RS256 |
| UI | Nuxt UI |
git clone https://github.com/nuxt-content/nuxt-studio-sso
cd nuxt-studio-sso
pnpm installCopy .env.example to .env and configure:
# Session encryption (minimum 32 characters)
NUXT_SESSION_PASSWORD=your-super-secret-session-password-here
# GitHub OAuth (for logging into the auth server)
NUXT_OAUTH_GITHUB_CLIENT_ID=your-github-client-id
NUXT_OAUTH_GITHUB_CLIENT_SECRET=your-github-client-secret
# JWT Keys (RS256) - see below for generation
NUXT_JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
NUXT_JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
# GitHub scope (optional, defaults to public_repo)
# Use comma-separated values, e.g. "repo" for full private repo access
GITHUB_SCOPE=public_repo# Generate RSA key pair
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
# Convert to single-line format for .env
cat private.pem | tr '\n' '\\n' | sed 's/\\n$//'
cat public.pem | tr '\n' '\\n' | sed 's/\\n$//'pnpm devVisit http://localhost:5000 and log in with GitHub. The first user to sign up automatically becomes the admin.
- First user is admin: The first person to sign up becomes the admin automatically
- Can create, edit, and delete OAuth clients
- Can view all registered clients and their settings
- Has access to OIDC configuration links
- Can log in and authorize OAuth requests from client sites
- Can view a list of all connected websites where they can sign in
- Cannot create or manage OAuth clients
Note: Only admin users can register OAuth clients.
- Log into the SSO server dashboard at
/dashboard - Navigate to Clients
- Click New Client
- Fill in:
- Client Name: Your site's name (e.g., "My Documentation Site")
- Website URL: Your production URL (e.g.,
https://docs.example.com) - Preview URL Pattern (optional): Pattern for preview deployments
- Click Create Client
- Important: Copy the client secret immediately — it's only shown once!
The callback URL (/__nuxt_studio/auth/sso) is automatically appended to your website URL.
For sites deployed on platforms like Vercel or Netlify, configure a preview URL pattern to allow authentication from preview deployments:
| Platform | Pattern Example |
|---|---|
| Vercel | https://*.vercel.app or https://my-project-*.vercel.app |
| Netlify | https://*.netlify.app or https://deploy-preview-*--my-site.netlify.app |
| Cloudflare Pages | https://*.pages.dev |
The * wildcard matches any characters in that position.
The Nuxt Studio module has built-in support for the SSO server. Set these environment variables on your Nuxt Studio site:
# SSO server credentials (from admin dashboard)
STUDIO_SSO_URL=https://auth.example.com
STUDIO_SSO_CLIENT_ID=<client_id from dashboard>
STUDIO_SSO_CLIENT_SECRET=<client_secret from dashboard>Users can then log in via the /__nuxt_studio/auth/sso route.
When users log in to the SSO server with GitHub, their GitHub access token is securely captured and passed to client sites. This eliminates the need for a separate STUDIO_GITHUB_TOKEN environment variable.
How it works:
- User logs into SSO server with GitHub (requests
public_reposcope by default) - SSO server encrypts and stores the GitHub token
- SSO server returns the GitHub token in the userinfo response
- Studio uses this token for Git operations
Configuring GitHub scope:
By default, the server requests public_repo scope (read/write access to public repositories only). To grant access to private repositories, set the GITHUB_SCOPE environment variable:
# Full private repo access
GITHUB_SCOPE=repo
# Multiple scopes (comma-separated)
GITHUB_SCOPE=repo,read:orgBenefits:
- No PAT required — users authenticate with their own GitHub account
- Each user's changes are committed with their own GitHub identity
- Tokens are updated automatically when users re-authenticate
1. User visits docs.example.com → clicks "Login"
│
▼
2. Redirect to auth.example.com/oauth/authorize
(with client_id, redirect_uri, state, code_challenge)
│
▼
3. User not logged in → shows login page
│
▼
4. User clicks "Continue with GitHub"
│
▼
5. GitHub OAuth flow → user authenticated
│
▼
6. Shows consent screen: "docs.example.com wants to access your account"
│
▼
7. User approves → authorization code generated
│
▼
8. Redirect to docs.example.com/__nuxt_studio/auth/sso?code=xxx&state=yyy
│
▼
9. Site A exchanges code + code_verifier for tokens → sets Studio session
│
▼
✅ User is logged into Site A
1. User visits blog.example.com → clicks "Login"
│
▼
2. Redirect to auth.example.com/oauth/authorize
│
▼
3. SSO server recognizes existing session!
│
▼
4. Shows consent screen (user already authenticated)
│
▼
5. User approves → authorization code generated
│
▼
6. Redirect to blog.example.com/__nuxt_studio/auth/sso?code=xxx&state=yyy
│
▼
7. Site B exchanges code for tokens → sets Studio session
│
▼
✅ User is logged into Site B (no password needed!)
| Endpoint | Method | Description |
|---|---|---|
/oauth/authorize |
GET | Authorization endpoint — initiates OAuth flow |
/oauth/token |
POST | Token endpoint — exchanges code for tokens |
/oauth/userinfo |
GET | Returns authenticated user info (requires Bearer token) |
/oauth/revoke |
POST | Revokes refresh tokens |
| Parameter | Required | Description |
|---|---|---|
client_id |
Yes | The registered client ID |
redirect_uri |
Yes | Must match the registered website URL callback |
response_type |
Yes | Must be code |
state |
Yes | CSRF protection token |
code_challenge |
Yes | PKCE code challenge (S256) |
code_challenge_method |
No | Must be S256 (default) |
| Parameter | Required | Description |
|---|---|---|
grant_type |
Yes | authorization_code or refresh_token |
code |
Yes* | The authorization code (*for authorization_code grant) |
redirect_uri |
Yes* | Must match the authorization request (*for authorization_code grant) |
code_verifier |
Yes* | PKCE code verifier (*for authorization_code grant) |
refresh_token |
Yes* | The refresh token (*for refresh_token grant) |
client_id |
Yes | Via body or Basic auth header |
client_secret |
Yes | Via body or Basic auth header |
The /oauth/userinfo endpoint returns user information:
| Field | Description |
|---|---|
sub |
User's unique identifier |
name |
User's display name |
email |
User's email address |
picture |
User's avatar URL |
github_token |
GitHub access token (if user logged in with GitHub) |
git_provider |
Set to "github" when GitHub token is available |
| Endpoint | Method | Description |
|---|---|---|
/.well-known/openid-configuration |
GET | OIDC discovery document |
/.well-known/jwks.json |
GET | JSON Web Key Set for token verification |
| Endpoint | Method | Description |
|---|---|---|
/api/clients |
GET | List all OAuth clients |
/api/clients |
POST | Create a new client |
/api/clients/:id |
GET | Get client details |
/api/clients/:id |
PATCH | Update client |
/api/clients/:id |
DELETE | Delete client |
/api/clients/:id/secret |
POST | Regenerate client secret |
| Endpoint | Method | Description |
|---|---|---|
/api/websites |
GET | List all connected websites (for authenticated users) |
This project uses NuxtHub for database management, which supports multiple cloud providers.
- Push your code to a GitHub repository
- Deploy to your preferred platform (Vercel, Netlify, etc.)
- Set the required environment variables
| Variable | Required | Description |
|---|---|---|
NUXT_SESSION_PASSWORD |
Yes | Session encryption key (minimum 32 characters) |
NUXT_OAUTH_GITHUB_CLIENT_ID |
Yes | GitHub OAuth App client ID |
NUXT_OAUTH_GITHUB_CLIENT_SECRET |
Yes | GitHub OAuth App client secret |
NUXT_JWT_PRIVATE_KEY |
Yes | RSA private key for JWT signing |
NUXT_JWT_PUBLIC_KEY |
Yes | RSA public key for JWT verification |
GITHUB_SCOPE |
No | GitHub OAuth scopes (default: public_repo) |
For database configuration, see the NuxtHub deployment docs.
- PKCE required: All authorization requests must include a
code_challenge(S256 only) - State required: The
stateparameter is mandatory for CSRF protection - Authorization codes hashed: Stored as SHA-256 hashes, not plaintext
- Client secrets hashed: Stored as SHA-256 hashes
- Refresh tokens hashed: Stored as SHA-256 hashes
- GitHub tokens encrypted: AES-256-GCM encryption at rest
- Timing-safe comparisons: All secret/hash comparisons use
timingSafeEqual - Access tokens: JWT with RS256 signing, 1-hour expiry
- Refresh tokens: 30-day expiry
- Token responses: Include
Cache-Control: no-storeper RFC 6749 - Always use HTTPS in production
PKCE is required. Ensure your client sends code_challenge and code_challenge_method=S256 in the authorization request, and code_verifier in the token exchange.
Ensure your website URL is correctly configured in the OAuth client settings. The callback path /__nuxt_studio/auth/sso is automatically appended.
The OAuth state cookie may have expired. Try the login flow again.
Double-check your STUDIO_SSO_CLIENT_ID and STUDIO_SSO_CLIENT_SECRET environment variables.
Only the first user to sign up becomes an admin. Contact your administrator if you need to manage OAuth clients.
Ensure the user has successfully authenticated with GitHub on the SSO server. The GitHub token is captured during the GitHub OAuth flow and stored encrypted in the database.
MIT