# OAuth clients (app.OAuthClients)

> `app.OAuthClients` declares the third-party OAuth providers your app uses — for signing users in (Login), for connecting to an external API on a user's behalf (Connection), and for your app holding its own machine credential (Service). Each is `new OAuthClient("Name")` naming a Provider (Google/Github/Microsoft/Oidc), the Capabilities, a ClientId, and a ClientSecret (a `Secret.X` handle). Referenced elsewhere by its `OAuthClient.Name` handle.

<!-- id: config-oauth-clients · area: config · stability: stable · html: https://osysharp.com/reference/config/oauth-clients/ -->

## Summary        {#summary}
`app.OAuthClients` declares the third-party OAuth providers your application talks to. There are two reasons to declare
one: to let users **sign in** with an external identity (Login), to **connect** to an external API and act on a
user's behalf (Connection), and for your app to hold **its own** credential with a service (Service). Each client is a `new OAuthClient("Name")` that names a `Provider`, the `Capabilities` you
need, a `ClientId`, and a `ClientSecret` (a `Secret.X` handle). The client's `Name` is how the rest of your app refers
back to it as `OAuthClient.Name`.

```osy syntax
app.OAuthClients = [
  new OAuthClient("Google") {
    Provider     = OAuthProvider.Google,
    Capabilities = [OAuthCapability.Login],
    ClientId     = "your-client-id",
    ClientSecret = Secret.GoogleOAuth,
  },
];
```

## Signature      {#signature}
```osy
using Osysharp.Oauth;                          // the capability the client types come from

app.Secrets = [ new Secret("GoogleOAuth") ];   // the handle the client below reads

app.OAuthClients = [
  new OAuthClient("Name") {            // one entry per provider client
    Provider     = OAuthProvider.Google,          // Google | Github | Microsoft | Oidc
    Capabilities = [OAuthCapability.Login, OAuthCapability.Connection],
    ClientId     = "your-client-id",              // the provider-issued client id
    ClientSecret = Secret.GoogleOAuth,            // a Secret.X handle, never a literal
  },
];
```

`app.OAuthClients` is a list — an app may declare several clients, one per provider (or several against the same
provider for different capabilities).

## Description    {#description}
An `OAuthClient` has:

- **`Provider`** — an `OAuthProvider` enum member naming the identity provider: `Google`, `Github`, `Microsoft`, or
  `Oidc` (a generic OpenID Connect provider).
- **`Capabilities`** — a list of `OAuthCapability`, and it decides *who the token belongs to*:
  - `Login` — users sign into your app with this provider.
  - `Connection` — your app calls the provider's API **as a signed-in user**, using a token that person granted.
  - `Service` — your app calls the API **as itself**, with no user involved. See [[#service]] below.

  A client may declare more than one.
- **`ClientId`** — a string: the client identifier the provider issued when you registered your app with it.
- **`ClientSecret`** — a `Secret.X` handle referring to a secret declared in [declaring secrets (app.Secrets)](https://osysharp.com/reference/config/secrets/). You never write the
  secret value inline; you reference the named secret.

The client's **`Name`** (the argument to `new OAuthClient("…")`) is its handle. That name is how the rest of your app
refers back to the client as `OAuthClient.Name` — for example an `OAuthAuth` method that signs users in via this
provider references the client by its `OAuthClient.Name` handle.

### When your app is the one with the account   {#service}

`Connection` borrows a token from the person who is signed in. Sometimes there is no such person — a scheduled job, a
workflow step, a webhook receiver — and the account belongs to your app rather than to any user. That is
`OAuthCapability.Service`: the *client-credentials* grant, where your `ClientId` and `ClientSecret` are themselves the
credential.

Declare the capability:

```osy syntax
app.Secrets = [ new Secret("WarehouseSecret") ];

app.OAuthClients = [
  new OAuthClient("Warehouse") {
    Provider     = OAuthProvider.Oidc,
    Capabilities = [OAuthCapability.Service],
    Issuer       = "https://example.cloud.databricks.com/oidc",   // the token endpoint is discovered from this
    Scopes       = ["all-apis"],
    ClientId     = "your-service-principal-id",
    ClientSecret = Secret.WarehouseSecret,
  },
];
```

Because no user is involved, a service credential works in exactly the places `ConnectedOAuth` cannot: a background
job, a workflow step, an `[AllowAnonymous]` function.

### Spending the token — `OAuthClient.<Name>.ServiceToken` / `.ConnectedToken`   {#spending}
A declared client's token is read as a **member of the client handle**, the same shape
[`Secret.<Name>`](https://osysharp.com/reference/function/secret-read/) uses — and you put it wherever the API wants it, usually an `Authorization`
header on a [typed client class](https://osysharp.com/reference/http/client/):

```osy title="spend the APP's own credential — no user involved" test app=oauth-clients-spending
app Warehouse {
  model "model/**/*.osy";
  use Osysharp.Http;
  use Osysharp.Oauth;
}

using Osysharp.Http;

app.Secrets = [ new Secret("WarehouseSecret") ];

app.OAuthClients = [
  new OAuthClient("Warehouse") {
    Provider     = OAuthProvider.Oidc,
    Capabilities = [OAuthCapability.Service],
    Issuer       = "https://example.cloud.databricks.com/oidc",
    Scopes       = ["all-apis"],
    ClientId     = "your-service-principal-id",
    ClientSecret = Secret.WarehouseSecret,
  },
];

string Clusters() {
  var headers = new Dictionary<string, string>();
  headers.Add("Authorization", "Bearer " + OAuthClient.Warehouse.ServiceToken);
  var r = Http.Get("https://example.cloud.databricks.com/api/2.0/clusters/list", headers);
  return r.IsSuccess ? r.Body : "";
}
```

Every read hands you a fresh-enough token; you never write one down and there is nothing to refresh by hand.

### Which verb — it is the PRINCIPAL, and the call site says so   {#which-verb}

| verb | whose authority the call carries | needs |
|---|---|---|
| **`.ServiceToken`** | the **app's own** — no user at all, so it works in a workflow step, a background job, an `[AllowAnonymous]` function | `Capabilities = [OAuthCapability.Service]` |
| **`.ConnectedToken`** | the **signed-in person's** — delegated, "as me" | `Capabilities = [OAuthCapability.Connection]`, and a signed-in caller who has connected that account |

⛔ **The verb is explicit rather than inferred from the declaration, and that is deliberate.** These two differ in
*whose authority an outbound call carries* — so a reviewer reading the call site must be able to see which without
opening another file, and a client may declare both capabilities anyway. A client declared only for
`OAuthCapability.Login` has neither grant: `Login` authenticates a person, it does not hand you anything to spend, and
asking it for either token is refused by name.

⚠ **`.ConnectedToken` fails where there is no person**, which is correct and is the commonest surprise: the same
function body works under a request and throws in a worker or a workflow step. That throw is
`catch (OAuthConnectionFailedException e)`-able, and it also fires when the user simply has not connected that account
yet, or has connected it without a scope you now need.

### A token is a credential, and is confined like one   {#confinement}
⛔ **You cannot return it, and you cannot put it on a screen.** `return OAuthClient.X.ServiceToken;` is refused
(`SECRET_ESCAPES`), directly or through a local, and a component body that reads one is refused because it would be
sent to the browser — where a bearer token is *worse* than a secret, being directly spendable against someone else's
API with nothing else needed. **Spend it where it is needed and return the RESULT**, exactly as with a
[secret](https://osysharp.com/reference/function/secret-read/).

⚑ **Why a token read is never stale on a durable resume.** An outbound call inside a workflow is a durable step, so
you might expect a memoized token to come back expired days later. It cannot: the read is its own node kind rather
than a stdlib call, and memoization is keyed on a call's durability classification — so a leaf that is not a call has
nothing to memoize against and is re-read on every evaluation. That also means a token is never *recorded* in a run's
durable history, which an `External` classification would have done.

⚠ **`Provider` must be `Oidc`, with an `Issuer`.** The client-credentials grant is an OpenID Connect mechanism, and
the token endpoint is discovered from the issuer. Google's and GitHub's consumer OAuth authenticate a *person* and
have no such grant — naming one is refused, by name, rather than failing at the first request.

⚠ **`Scopes` are sent with the grant.** A service token with the wrong scopes is refused by the service you are
calling, not by your app, which is the least helpful place to find out — so declare them here.

### The capability: `use Osysharp.Oauth;`     {#capability}
Per-user OAuth links are a **capability**, `Osysharp.Oauth`. Declaring an `OAuthClient` sets up the *provider*;
the capability adds the per-**user** side: a `UserOAuthLink` entity that ties one of your app's users to the identity
(and, for a `Connection` client, the stored tokens) they hold with a provider. The platform writes these links when a
user signs in or connects; the `use`/`using` gates whether your own code may name and query them.

- **`use Osysharp.Oauth;`** in the `app { }` manifest declares the dependency.
- **`using Osysharp.Oauth;`** at the top of a source file imports the `UserOAuthLink` name so a function or
  query in that file may reference a user's linked provider identities.

See [use](https://osysharp.com/reference/types/use/) for how `use` (the dependency) and `using` (the file-level import) differ.

## Examples       {#examples}
Declare the secret, then declare a Google client that lets users sign in:

```osy title="basic" test app=config-oauth-example
using Osysharp.Oauth;

app.Secrets = [ new Secret("GoogleOAuth") ];

app.OAuthClients = [
  new OAuthClient("Google") {
    Provider = OAuthProvider.Google,
    Capabilities = [OAuthCapability.Login],
    ClientId = "your-client-id",
    ClientSecret = Secret.GoogleOAuth,
  },
];
```

## See also       {#see-also}
- [declaring secrets (app.Secrets)](https://osysharp.com/reference/config/secrets/) — `app.Secrets`, where the `Secret.X` used by `ClientSecret` is declared
- [[AuthMethod] — a function an unauthenticated visitor may call](https://osysharp.com/reference/security/auth-method/) — declaring an auth method that signs users in via an `OAuthClient`
- [auth bootstrap (login, before anyone is signed in)](https://osysharp.com/reference/security/auth-bootstrap/) — granting the first user their role after an OAuth sign-in
- [use](https://osysharp.com/reference/types/use/) — `use Osysharp.Oauth;` (the dependency) and `using Osysharp.Oauth;` (the import)
