A lightweight, standalone service that acts as a universal GitHub OAuth redirect handler. This service allows you to configure one GitHub OAuth callback URL that works across all your environments (local, staging, production).
- User initiates OAuth flow from your app (any environment)
- Your frontend calls your backend endpoint
- Your backend (with OAUTH_SERVICE_TOKEN) calls
POST /github/authorize-url
- Service validates the token and returns the complete GitHub authorization URL
- Your backend returns this URL to your frontend
- Your frontend redirects:
window.location.href = authorize_url
- GitHub redirects to this service after user authorization
- Service receives the
code
and signedstate
from GitHub
- Service receives the
- Service validates and exchanges
- Verifies the HMAC signature of the state parameter (signed with OAUTH_SERVICE_TOKEN)
- Ensures the state hasn't expired (10 minute limit)
- Exchanges code for access token with GitHub
- Service redirects back to original environment
- Redirects to the validated return URL from the state
- Includes the
github_token
as a query parameter
- Your backend calls
POST /create-state
to get just the signed state - Your frontend manually builds the GitHub authorization URL
- Requires exposing
GITHUB_CLIENT_ID
to your main app (3 env vars instead of 2)
GITHUB_CLIENT_ID
- Your GitHub OAuth application client IDGITHUB_CLIENT_SECRET
- Your GitHub OAuth application client secretOAUTH_SERVICE_TOKEN
- Single authentication token (used for both authentication AND state signing)
APP_REDIRECT_FALLBACK
- Fallback redirect URL ifstate
is missing (default:http://localhost:3000
)PORT
- Port to run the service on (default:3000
)
Generate a secure random token:
# Generate a token
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
GITHUB_CLIENT_ID=Iv1.abc123
GITHUB_CLIENT_SECRET=your_secret_here
# Single authentication token (used for both auth and state signing)
OAUTH_SERVICE_TOKEN=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
- Secure storage: Store the token as an environment variable, never commit it to version control
- Rotation: Regularly rotate the token, especially if compromised
- Same token for all apps: Use the same
OAUTH_SERVICE_TOKEN
value in both the OAuth redirect service and all your apps that use it
- Install dependencies:
npm install
- Create a
.env
file (copy from.env.example
):
cp .env.example .env
-
Edit
.env
and add your GitHub OAuth credentials -
Run the development server:
npm run dev
The service will be available at http://localhost:3000
This service is designed to be deployed independently to any hosting platform. Here are guides for popular platforms:
- Install Vercel CLI:
npm i -g vercel
- Run
vercel
in theoauth-redirect
directory - Set environment variables in Vercel dashboard
- GitHub callback URL:
https://your-service.vercel.app/github/callback
- Create a new Web Service on Render
- Connect your repository
- Set Root Directory to
oauth-redirect
- Set Build Command to
npm install
- Set Start Command to
npm start
- Add environment variables in Render dashboard
- GitHub callback URL:
https://your-service.onrender.com/github/callback
- Create
netlify.toml
in theoauth-redirect
folder:
[build]
command = "npm install"
publish = "."
functions = "netlify/functions"
[[redirects]]
from = "/*"
to = "/.netlify/functions/server/:splat"
status = 200
- Convert to serverless function (or use Netlify Functions)
- Deploy via Netlify CLI or GitHub integration
- Add environment variables in Netlify dashboard
- Create a new project on Railway
- Connect your repository
- Set Root Directory to
oauth-redirect
- Add environment variables
- Railway will auto-detect Node.js and deploy
- GitHub callback URL:
https://your-service.up.railway.app/github/callback
- Use Cloudflare Workers or Pages Functions
- Adapt the Express code to Workers format
- Deploy via Wrangler CLI
- Add environment variables as secrets
- Go to GitHub Settings → Developer settings → OAuth Apps
- Create a new OAuth App (or edit existing)
- Set Authorization callback URL to:
https://your-deployed-service.com/github/callback
- Note your Client ID and Client Secret
Returns the complete GitHub authorization URL. This is the simplest integration method.
Authentication:
- Requires
Authorization: Bearer <token>
header - Token must match
OAUTH_SERVICE_TOKEN
Request Body:
{
"return_url": "https://your-app.com",
"scope": "user:email repo"
}
Response:
{
"authorize_url": "https://github.com/login/oauth/authorize?client_id=...&redirect_uri=...&state=...&scope=...",
"state": "base64url_encoded_signed_state"
}
Usage:
Your backend calls this endpoint, then returns the authorize_url
to your frontend. The frontend simply redirects: window.location.href = authorize_url
Security:
- Main app only needs 2 environment variables:
OAUTH_REDIRECT_BASE
andOAUTH_SERVICE_TOKEN
- No need to expose
GITHUB_CLIENT_ID
to your main app
Handles GitHub OAuth callbacks.
Query Parameters:
code
(required) - Authorization code from GitHubstate
(required) - Cryptographically signed state parameter containing the return URL
Response:
- Validates the signed state parameter
- Ensures state hasn't expired
- Exchanges code for access token
- Redirects to validated URL with
github_token
query parameter
Security:
- State must be HMAC-signed with
OAUTH_SERVICE_TOKEN
- State expires after 10 minutes
- Protects against open redirect and token exfiltration attacks
Creates a cryptographically signed state parameter for initiating OAuth flows. Use /github/authorize-url
instead for simplified integration.
Authentication:
- Requires
Authorization: Bearer <token>
header - Token must match
OAUTH_SERVICE_TOKEN
Request Body:
{
"return_url": "https://your-app.com"
}
Response:
{
"state": "base64url_encoded_signed_state"
}
Security:
- Validates authentication token before creating state
- Returns 401 if token is invalid or missing
⚠️ IMPORTANT: Never expose tokens to the frontend - this endpoint must be called from your backend only
Health check endpoint.
Response:
{
"status": "ok",
"service": "oauth-redirect-service",
"tokensConfigured": 3,
"stateSecretConfigured": true
}
Service information page showing configuration status and security features.
Only applications with a valid authentication token can create signed states and initiate OAuth flows. This provides:
- No domain configuration needed
- Works on any hosting platform
- Per-environment access control
- Easy token revocation
This service uses OAUTH_SERVICE_TOKEN
for both:
- Authentication: Validates requests from your backend
- State Signing: HMAC-SHA256 signing of state parameters
Benefits:
- Simplified configuration (only 2 env vars needed in main app)
- Single token to manage and rotate
- No separate state secret to synchronize
The service uses HMAC-SHA256 to cryptographically sign state parameters using OAUTH_SERVICE_TOKEN
, preventing attackers from forging redirect URLs. Each state includes:
- The return URL
- A timestamp (expires after 10 minutes)
- A cryptographic signature
The signed state parameter provides strong CSRF protection, ensuring that OAuth callbacks can only complete if initiated from your service with a valid token.
- Always use HTTPS in production
- Keep your
GITHUB_CLIENT_SECRET
andOAUTH_SERVICE_TOKEN
secure and private - Set
OAUTH_SERVICE_TOKEN
to a strong random value (minimum 32 characters) - Regularly rotate
OAUTH_SERVICE_TOKEN
if compromised (update in both services) - Access tokens are passed as query parameters - consider implementing secure session-based handoff for production
- NEVER expose OAUTH_SERVICE_TOKEN to the frontend - tokens must stay server-side only
- Create a backend endpoint that calls the OAuth redirect service on behalf of your frontend
- Frontend calls your backend to get signed states, never the OAuth service directly
Frontend → Your Backend → OAuth Redirect Service
(token here) (validates token, creates state)
See INTEGRATION.md
for complete integration examples with security best practices.
MIT