Walks through configuring Google, Apple, Microsoft, GitHub, and other providers to accept localhost or portless redirect URIs, fixing mismatch errors and callback routing.
Best for: Engineers tired of fighting OAuth redirect-URI mismatches on localhost.
Creator's repository · vercel-labs/portless
License: Apache-2.0
---
name: oauth
description: Configure OAuth providers (Google, Apple, Microsoft, Facebook, GitHub, etc.) to work with portless local dev URLs. Use when setting up OAuth redirect URIs, fixing "redirect_uri_mismatch" or "invalid redirect" errors, configuring sign-in providers for local development, or when a provider rejects .localhost subdomains. Triggers include "OAuth not working with portless", "redirect URI mismatch", "Google/Apple/Microsoft sign-in fails locally", "configure OAuth for local dev", or any task involving OAuth callback URLs with portless domains.
---
# OAuth with Portless
OAuth providers validate redirect URIs against domain rules. `.localhost` subdomains fail on most providers because they are not in the Public Suffix List or are explicitly blocked. Portless fixes this with `--tld` to serve apps on real, valid domains.
## The Problem
When portless uses the default `.localhost` TLD, OAuth providers reject redirect URIs like `http://myapp.localhost:1355/callback`:
| Provider | `localhost` | `.localhost` subdomains | Reason |
| --------- | ----------- | ----------------------- | ------------------------------ |
| Google | Allowed | Rejected | Not in their bundled PSL |
| Apple | Rejected | Rejected | No localhost at all |
| Microsoft | Allowed | Allowed | Permissive localhost handling |
| Facebook | Allowed | Varies | Must register each URI exactly |
| GitHub | Allowed | Allowed | Permissive |
Google and Apple are the strictest. Microsoft and GitHub are more lenient with localhost.
## The Fix
Use a valid TLD so the redirect URI passes provider validation:
```bash
portless proxy start --tld dev
portless myapp next dev
# -> https://myapp.dev
```
Any TLD in the Public Suffix List works: `.dev`, `.app`, `.com`, `.io`, etc.
### Use a domain you own
Bare TLDs like `.dev` mean `myapp.dev` could collide with a real domain. Use a subdomain of a domain you control:
```bash
portless proxy start --tld dev
portless myapp.local.yourcompany next dev
# -> https://myapp.local.yourcompany.dev
```
This ensures no outbound traffic reaches something you don't own. For teams, set a wildcard DNS record (`*.local.yourcompany.dev -> 127.0.0.1`) so every developer gets resolution without `/etc/hosts`.
## Provider Setup
### Google
1. Go to [Google Cloud Console > Credentials](https://console.cloud.google.com/apis/credentials)
2. Create or edit an OAuth 2.0 Client ID (Web application)
3. Add the portless domain to **Authorized JavaScript origins**: `https://myapp.dev`
4. Add the callback to **Authorized redirect URIs**: `https://myapp.dev/api/auth/callback/google`
Google validates domains against the Public Suffix List. The domain must end with a recognized TLD. `.localhost` subdomains fail this check; `.dev`, `.app`, `.com`, etc. all pass.
HTTPS is required for `.dev` and `.app` (HSTS-preloaded). Portless handles this automatically with `--https`.
### Apple
Apple Sign In does not allow `localhost` or IP addresses at all.
1. Go to [Apple Developer > Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources)
2. Register a Services ID
3. Configure Sign In with Apple, adding the portless domain as a **Return URL**: `https://myapp.dev/api/auth/callback/apple`
The domain must be a real, publicly-resolvable domain name. Since portless maps the domain to 127.0.0.1 locally, the browser resolves it but Apple's server-side validation may require the domain to resolve publicly too. If Apple rejects the domain, add a public DNS A record pointing to 127.0.0.1 for your dev subdomain.
### Microsoft (Entra / Azure AD)
1. Go to [Azure Portal > App registrations](https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps)
2. Create or edit an app registration
3. Under **Authentication**, add a **Web** redirect URI: `https://myapp.dev/api/auth/callback/azure-ad`
Microsoft allows `http://localhost` with any port for development. It also accepts `.localhost` subdomains in most cases. Using a custom TLD with portless is still recommended for consistency across providers.
### Facebook (Meta)
1. Go to [Meta for Developers > App Dashboard](https://developers.facebook.com/apps/)
2. Under **Facebook Login > Settings**, add the portless URL to **Valid OAuth Redirect URIs**: `https://myapp.dev/api/auth/callback/facebook`
Facebook requires each redirect URI to be registered exactly (no wildcards). Strict Mode (enabled by default) enforces exact matching.
### GitHub
1. Go to [GitHub Developer Settings > OAuth Apps](https://github.com/settings/developers)
2. Set **Authorization callback URL**: `https://myapp.dev/api/auth/callback/github`
GitHub is permissive with localhost and subdomains. A custom TLD is not strictly required but keeps the setup consistent.
## Auth Library Configuration
### NextAuth / Auth.js
Set `NEXTAUTH_URL` to match the portless domain:
```env
NEXTAUTH_URL=https://myapp.dev
```
NextAuth uses this to construct callback URLs. Without it, callbacks may use `localhost` and cause a mismatch.
### Passport.js
Set the `callbackURL` in each strategy to use the portless domain:
```js
new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.BASE_URL + "/auth/google/callback",
});
```
Set `BASE_URL=https://myapp.dev` in your environment.
### Generic / Manual
Read the `PORTLESS_URL` environment variable that portless injects into the child process:
```js
const baseUrl = process.env.PORTLESS_URL || "http://localhost:3000";
const callbackUrl = `${baseUrl}/auth/callback`;
```
## Troubleshooting
### "redirect_uri_mismatch" or "invalid redirect URI"
The redirect URI sent during the OAuth flow doesn't match what's registered with the provider. Check:
1. The provider's registered redirect URI matches the portless domain exactly (protocol, host, path)
2. `NEXTAUTH_URL` or equivalent is set to the portless URL (not `localhost`)
3. The proxy is running with the correct TLD (`portless list` to verify)
### Provider requires HTTPS
`.dev` and `.app` TLDs are HSTS-preloaded, so browsers force HTTPS. Start the proxy:
```bash
portless proxy start --tld dev
```
Portless defaults to HTTPS on port 443 (auto-elevates with sudo). Run `portless trust` to add the local CA to your system trust store and eliminate browser warnings.
### Apple rejects the domain
Apple may require the domain to resolve publicly. Add a DNS A record for your dev subdomain pointing to `127.0.0.1`:
```
myapp.local.yourcompany.dev A 127.0.0.1
```
Or use a wildcard: `*.local.yourcompany.dev A 127.0.0.1`.
### Callback goes to wrong URL after sign-in
The auth library is constructing the callback URL from `localhost` instead of the portless domain. Set the appropriate environment variable:
- **NextAuth**: `NEXTAUTH_URL=https://myapp.dev`
- **Auth.js v5**: `AUTH_URL=https://myapp.dev`
- **Manual**: `PORTLESS_URL` is injected automatically; use it as the base URL
## Example
See [`examples/google-oauth`](../../examples/google-oauth) for a complete working example with Next.js + NextAuth + Google OAuth using `--tld dev`.