---
id: gh-oauth
name: "oauth"
url: https://skills.yangsir.net/skill/gh-oauth
author: vercel-labs
domain: ai-dev-tools-workflow
tags: ["oauth", "portless", "local-development", "redirect-uri", "devops"]
install_count: 3000
rating: 4.40 (120 reviews)
github: https://github.com/vercel-labs/portless/tree/main/skills/oauth
---

# oauth

> 解决使用 Portless 进行本地开发时，OAuth 提供商（如 Google, Apple）因 `.localhost` 域名限制导致的重定向 URI 错误。通过配置 Portless 使用有效 TLD，确保 OAuth 登录顺畅，避免“redirect_uri_mismatch”等问题。

**Stats**: 3,000 installs · 4.4/5 (120 reviews)

## Before / After 对比

### 解决 OAuth 重定向 URI 错误

**Before**:

在本地开发环境中，OAuth 提供商（如 Google, Apple）拒绝 `.localhost` 域名，导致重定向 URI 匹配失败，无法正常进行登录测试，耗费大量时间排查和寻找变通方案。

**After**:

通过 Portless 配置有效 TLD，OAuth 提供商能正确识别重定向 URI，本地开发环境下的登录流程顺畅无阻，大幅提升开发效率。

| Metric | Before | After | Change |
|---|---|---|---|
| 调试时间 | 120分钟 | 10分钟 | -91.67% |

## Readme

# 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`.


---
*Source: https://skills.yangsir.net/skill/gh-oauth*
*Markdown mirror: https://skills.yangsir.net/api/skill/gh-oauth/markdown*