> ## Documentation Index
> Fetch the complete documentation index at: https://docs.theauth.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Path Prefix and Dynamic Providers

> Two options for apps that already own their public URLs: serve the library under a prefix of your choosing, and let OAuth/OIDC providers change at runtime.

Two options for apps that already own their public URLs: serve the library under
a prefix of your choosing, and let OAuth/OIDC providers change at runtime.

## Path prefix

`Config.PathPrefix` sets where `Mount` and `Handler` serve the auth routes. The
default is `/auth`, so existing apps are unchanged.

```go theme={"dark"}
a, err := theauth.New(theauth.Config{
    Storage:    store,
    BaseURL:    "https://app.example.com",
    PathPrefix: "/api/v1/auth",
})
mux.Handle("/", a.Handler()) // no http.StripPrefix needed
```

The prefix must start with `/`, must not end with `/`, and must not contain a
query, fragment, wildcard, whitespace or empty/dot segments. Anything else makes
`New` return an error.

Every URL the library generates follows the prefix:

| Generated value | Example with `/api/v1/auth` |
| - | - |
| OAuth redirect URI | `https://app.example.com/api/v1/auth/providers/github/callback` |
| Magic link | `https://app.example.com/api/v1/auth/magic-link/verify?token=...` |
| Password reset link | `https://app.example.com/api/v1/auth/email-password/reset?token=...` |
| WebAuthn challenge cookie `Path` | `/api/v1/auth/webauthn` |
| Authorization server default `LoginURL` | `/api/v1/auth/login` |

Register the redirect URI shown above at each identity provider. Routes outside
the prefix (`/oauth/*`, `/.well-known/*`, `/scim/v2/*`, `/admin/v1/*`) keep their
own paths. The device verification page stays at `BaseURL + "/device"` unless
`DeviceConfig.VerificationURI` is set.

### clientauth

`clientauth.DeviceOptions.AuthPath` and `clientauth.Client.AuthPath` take the same
prefix. They default to `clientauth.DefaultAuthPath` (`/auth`).

```go theme={"dark"}
clientauth.DeviceLogin(ctx, clientauth.DeviceOptions{
    ServerURL: "https://app.example.com",
    AuthPath:  "/api/v1/auth",
})
```

## Custom OAuth redirect URI

By default the redirect URI sent to a provider is
`BaseURL + prefix + /providers/{name}/callback`. An app migrating from another
auth stack often has different callback URLs already registered at Google,
GitHub, Microsoft or an OIDC issuer. `OAuthConfig.RedirectURI` overrides the
value without touching the registered URLs.

```go theme={"dark"}
a, err := theauth.New(theauth.Config{
    // ...
    OAuth: &theauth.OAuthConfig{
        RedirectURIAllowedHosts: []string{"app.example.com"},
        RedirectURI: func(r *http.Request, provider string) (string, error) {
            return "https://app.example.com/api/v1/auth/oauth/" + provider + "/callback", nil
        },
    },
})
```

The hook takes the request so an app can derive the URI per request, and runs
once at start. The result is stored with the flow state and reused verbatim at
code exchange, so concurrent flows never share a value. With the hook unset,
behavior is unchanged.

The result must be an absolute URL with no fragment or userinfo. `https` is
required, except `http` on a loopback host or when
`OAuthConfig.AllowInsecureRedirectURI` is set (development only). Any hook
error or invalid value fails the start with a 500: no redirect, no state.

<Warning>
  **Do not trust the Host header.**
  Building the URI from `r.Host` or `X-Forwarded-Host` lets a spoofed header
  steer the authorization code to another host. Prefer a fixed URI, and set
  `RedirectURIAllowedHosts` (matched case-insensitively against the host or
  host:port) so any other host fails closed.
</Warning>

If your app serves its own callback route at a different path, call
`(*TheAuth).OAuthStart(r, provider, returnTo)` and
`(*TheAuth).OAuthCallback(r, provider, code, state, binding)`. You set the
binding cookie from `OAuthStartResult.Binding` and the session cookie from
`OAuthCallbackResult.SessionToken`. The built-in routes keep working.

## Dynamic providers

`Config.ProviderResolver` supplies providers that are not known at startup.

```go theme={"dark"}
type ProviderResolver interface {
    Resolve(ctx context.Context, name string) (Provider, bool, error)
}
```

Return `(nil, false, nil)` for an unknown name. A resolver may also implement
`List(ctx) ([]Provider, error)`, which `(*TheAuth).ListProviders` includes.

```go theme={"dark"}
a, err := theauth.New(theauth.Config{
    // ...
    EncryptionKey:       key,            // required with a resolver
    ProviderResolver:    dbResolver,
    ProviderResolverTTL: 30 * time.Second,
})

// After an admin edits or removes a provider:
a.InvalidateProvider("corp-sso")
```

Behavior:

* Static `Providers` win. Set `ProviderResolverFirst` to consult the resolver
  first, falling back to static providers when it reports not found.
* Names must match `^[a-z0-9][a-z0-9_-]{0,62}$` before they reach the resolver.
  Other names get a 404.
* A resolver error fails the flow closed (503 on start, no fallback, not cached).
* The returned provider's `Name()` must equal the requested name.
* `ProviderResolverTTL` caches answers, including "not found". Zero disables
  caching. `InvalidateProvider` drops one entry immediately and also discards any
  lookup that was in flight.
* State, PKCE, nonce and browser binding are unchanged. A provider removed
  between start and callback fails the callback.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.