# Applications and Federation

From [Descope Fundamentals](https://learn.descope.io/c/foundations) — Learn the platform by configuring your own project — every module ends in work the app checks for real.

Take this course interactively at https://learn.descope.io/c/foundations/applications-and-federation

---

![Identity Federation Patterns. 5 minutes.](video:applications-and-federation-identity-federation-patterns)

## The three directions, one line each

- Federated App — Descope is the identity provider, and another application trusts a sign-in that happened here
- Inbound App — Descope is the authorization server, and another application gets scoped access to your API
- Outbound App — Descope is a token vault, and your application gets scoped access to somebody else's API

The three preceding pages covered how each is configured. This one is about choosing between them, which turns out to be a different skill: they are not competing answers to one question, and most real projects run at least two of them at once. The reliable test is to ask who owns the resource being protected. If it belongs to the other side, you are federating identity outward. If it belongs to you, you are the authorization server. If the thing you need is a credential at somebody else's API, none of that applies and you want the vault.

## One customer, several directions at once

- Their employees sign in to your product — a Federated App, when your product is a separate Service Provider
- Their automation calls your API — an Inbound App, usually with the client credentials grant
- Their admins connect a Google Workspace so your product can read calendars — an Outbound App

A single enterprise customer routinely generates all three, configured independently of one another. The mistake to avoid is treating one as implying the others: a Federated App says nothing about API access, and an Inbound App says nothing about who may log in. They are separate grants to the same organization, and revoking one leaves the others standing.

## Partners and third-party developers

- A partner platform your users sign in to — a Federated App, with Descope as the identity provider
- A partner building against your API — an Inbound App, with scopes defined on a Resource
- An AI agent or MCP client that registers itself — an Inbound App created through Dynamic Client Registration

Partner integrations are where the ownership test does the most work, because both sides have users and both sides have APIs. Resolve it one integration at a time rather than once for the relationship. The same partner can be an SP for your identity in the morning and a client against your API in the afternoon, and those are two objects in the Console, not one.

## Internal tools

- An internal dashboard, wiki, or admin console — a Federated App, OIDC or SAML, usually from the Application Library
- A service you own calling an API you own — an access key or client credentials, which is not federation at all

The Application Library earns its place here: most internal tools are off-the-shelf products with a documented SSO setup, and a template removes the endpoint-copying. The second bullet is the one that gets over-engineered. Standing up an Inbound App so your own backend can call your own API adds an authorization server to a problem that did not have one; a machine credential is the smaller correct answer.

## Multi-app environments

- One project carries many Federated Apps, each with its own claims and its own assigned users
- The application ID reaches flows as `ssoAppID`, so a single flow can behave differently per application
- Inbound App scopes live on a shared Resource, so several clients reference one scope catalog rather than each owning its own

The question in a multi-app estate is how much to centralize. Claims and app assignment are necessarily per application. The login experience is not: one flow branching on `ssoAppID` is usually easier to keep coherent than six flows that start identical and drift. Scopes push the same way — defining them on the Resource means a new client inherits the catalog instead of inventing a parallel one.

## When none of the three is the answer

- The other side speaks neither OIDC nor SAML — a connector or a direct API integration fits better
- You only need to know who somebody is at sign-in — that is social login or SSO, not an Outbound App
- The caller is a machine with no user behind it — access keys and client credentials, and note that an unattended caller can only ever reach tenant-level Outbound tokens

## Summary

- Which of the three application features a given situation calls for
- That the owner of the protected resource decides the direction
- That one customer commonly needs more than one of them at the same time
- What to reach for when none of the three fit

---

![Inbound Apps. 4 minutes.](video:applications-and-federation-inbound-apps)

## Grant types and their use

- authorization_code — user-delegated access after interactive login and consent
- refresh_token — renew an access token without re-consent
- client_credentials — machine-to-machine tokens, no user present
- urn:ietf:params:oauth:grant-type:jwt-bearer — exchange a trusted external JWT for a Descope-issued token
- urn:ietf:params:oauth:grant-type:token-exchange — trade a token the caller holds for one scoped to a specific downstream Resource

Every Inbound App is created as one of two client types, and the type cannot be changed afterwards. Confidential clients are for apps that can securely store a secret — backend servers, server-side web apps — and token refresh requires the client_secret; apps created directly in the Console are always confidential. Non-confidential (public) clients are for apps that cannot store a secret, such as SPAs, mobile apps, and CLIs; they rely on PKCE and refresh using only the refresh token.

Apps can be created manually — Console, API/SDK, or Terraform — or automatically through Dynamic Client Registration, which is what lets MCP clients like Cursor or Claude Desktop register themselves on the fly. DCR-registered clients are always non-confidential, and confidential clients cannot be created through DCR.

> [!WARNING]
> A manually created Inbound App does not automatically get access to anything. It also needs a Policy whose subject is the app and whose target is the Resource it should reach. Clients registering through DCR skip this step — they automatically get access to the MCP server Resource they registered with.

Scopes are defined once on a Resource, either an API Resource or an MCP Server Resource, and Inbound Apps reference that Resource rather than owning their own scope catalog. On the app itself you configure two kinds: permission scopes, controlling what actions the app can take on behalf of the user or tenant and mapped to Descope RBAC roles on API Resources; and User Information scopes, controlling which user attributes are shared with the third-party application.

Consent is enforced through a flow. Two purpose-built components — Inbound App Logo and Inbound App Scopes — render the app's identity and requested permissions, a check against thirdPartyApp.user.consented decides whether to show the consent screen at all, and an Update User Consent action records the result, optionally with a time-based expiration after which the user re-consents. If a scope requires a role the user does not have, they simply cannot grant consent for it.

## Key terms

- Inbound App — the reverse of a Federated App: Descope as the authorization server guarding your APIs.
- Confidential vs. non-confidential client — whether the app can securely store a secret, or must rely on PKCE.
- Dynamic Client Registration (DCR) — self-service registration for clients that must register on the fly; always non-confidential.

![An Inbound App's settings — Descope acting as the authorization server for a third party that wants access to your users' data. Confidential versus Public is the first decision, because a public client cannot keep a secret and should have Require PKCE on. The Client ID and the secrets below it are what the calling app authenticates with, and the Consents tab beside Settings is where the grants users have given are listed.](applications-and-federation-inbound-apps)

## Summary

- Which grant type fits interactive consent, renewal, and machine-to-machine
- Why client type is a permanent decision at creation
- That a manual app needs an explicit Policy before it can reach anything
- Where scopes are defined, and how consent is actually enforced

---

![Outbound Apps. 5 minutes.](video:applications-and-federation-outbound-apps)

## What an Outbound App is for

- Your application needs to call a third-party API — Google Calendar, Slack, GitHub — as the user rather than as itself
- That means holding an access token issued by that provider, for that user, and refreshing it before it expires
- An Outbound App makes Descope hold those tokens, and hand one over when you ask for it

This is not a way to sign users in, and that is the distinction worth fixing early. Social login answers the question of who somebody is, once, at the moment they arrive. An Outbound App is about what your application may do at a provider afterwards — minutes or months later, with the user nowhere near their keyboard. The same provider can appear in both roles in one project, and the two configurations are unrelated.

## Where it lives in the Console

- Outbound Apps — Connect → Outbound Apps, which is this lesson
- Federated Apps — Identity Federation → Applications, where Descope is the identity provider
- Agentic Identity Hub Connections — a third object again, which Outbound Apps do not appear under

Creating one starts either from a provider template in the outbound app library or from a custom connection. Configuration is split across Outbound App Details, Account Information, Additional Settings, and Token Management. Under Account Information → Connection Settings you supply the Client ID and Client Secret you registered with the provider, and optionally a Callback Domain; Descope gives you the callback URL to register on the provider's side. Under Account Information → Scopes you declare the OAuth scopes the app may request, which have to be permitted at the provider as well. Additional Settings carries the provider's Authorization Endpoint and Token Endpoint, both required, and an optional Redirect URL that a flow or an SDK call can override.

There is also a Custom API Key App, for providers that issue a static key rather than running an OAuth exchange. It stores that secret per user or per tenant and is collected through the same flows and APIs as an OAuth connection.

## Three ways to connect a user

- A Flow action — Outbound App / Connect for a user, Outbound App / Tenant Connect for a tenant, plus Connect API key and Connect tenant API key for the static-secret case
- The frontend SDK — `sdk.outbound.connect('<app-id>', { redirectURL })`, which redirects to the provider and handles the OAuth round trip
- The REST API — `POST /v1/outbound/oauth/connect`, which returns a URL to send the user to

There is also an Outbound Applications widget, which lists the apps a user holds tokens for and offers a connect control for each. It runs entirely on the frontend, and that is also its limit: it requests the app's default scopes and cannot ask for a different set per user.

Scopes are worth deciding deliberately rather than by default. Calling `connect` without scopes uses the defaults configured in the Console; passing `scopes` overrides them. That is what makes incremental consent possible — ask for the minimum at first contact and come back for more when a feature actually needs it, rather than presenting a long permission list to somebody who has not yet seen the value.

## Fetching a token

- `fetchToken(appId, userId)` — the latest user token, and the one to reach for by default
- `fetchTokenByScopes(appId, userId, scopes)` — a user token carrying a specific set of scopes
- `fetchTenantToken(appId, tenantId)` and `fetchTenantTokenByScopes(...)` — the tenant-level equivalents

```javascript title="Calling a provider on the user's behalf"
const stored = await descopeClient.management.outboundApplication.fetchToken(
  "google-calendar",
  userId,
);

const events = await fetch("https://www.googleapis.com/calendar/v3/calendars/primary/events", {
  headers: { Authorization: `Bearer ${stored.data.accessToken}` },
});
```

The response carries more than the token itself: `accessTokenExpiry`, `hasRefreshToken`, `lastRefreshTime`, and `lastRefreshError`. That last field is the one to read when a working integration quietly stops working. A refresh that has been failing for a week — a revoked grant, a changed secret — looks exactly like a user who never connected, unless something checks it.

Two calling identities can fetch a stored token: a Management Key, or an Inbound App access token carrying the `outbound.token.fetch` scope. The second exists so a third-party application can reach tokens without being handed your management credentials, and it works only for tokens obtained through the authorization code grant. A client credentials token cannot retrieve Outbound App tokens at all.

> [!WARNING]
> Outbound tokens are keyed to a user or to a tenant, so an unattended process — anything authenticating with client credentials — has no user to fetch for and can reach tenant-level tokens only. The API answers a machine caller asking for a user token with a 404, and at least one SDK helper turns that 404 into a null rather than an error. The symptom is an integration reporting "not connected" for a user who is, in fact, connected.

## Key terms

- Token vault — Descope holding a provider's access and refresh tokens per user or tenant, and refreshing them on a schedule you did not have to write
- Incremental scopes — beginning a connection with minimal permissions and requesting more only when a feature needs them
- `outbound.token.fetch` — the Inbound App scope that lets an access token, rather than a Management Key, retrieve a stored token

## Summary

- That an Outbound App stores provider tokens, and is not a way to sign users in
- Which Console area each of the three application features lives under
- The three routes that connect a user, and the one thing the widget cannot do
- Why a client credentials caller can only ever reach tenant-level tokens

---

![Federated Apps. 4 minutes.](video:applications-and-federation-outbound-federated-apps)

> [!WARNING]
> Three similarly-named Console features are easy to confuse. Federated Apps — this lesson, where Descope acts as the IdP for downstream apps — live under Identity Federation → Applications. Outbound Apps, a token vault for connecting your users to third-party OAuth providers like Google or Slack, live under Connect → Outbound Apps and get their own lesson two pages on. Agentic Identity Hub → Connections is a third object again. None of them appear under another's area, and they share no configuration.

## Settings on every Federated App

- Application Name / ID / Description — the Application ID is available in flows as the ssoAppID variable, so you can render app-specific logic.
- Flow Hosting URL — where users land to authenticate; defaults to Descope's hosted flow page, or point it at a self-hosted flow.
- Force Authentication — re-runs the flow even if the user already has a session, equivalent to prompt=login.

For an OIDC application, Descope exposes an Issuer URL and a Discovery URL returning the full configuration as JSON — most OIDC clients self-configure from that single URL. The SP's Client ID is your Descope Project ID and its Client Secret is a Descope Access Key. Scopes control what comes back in the ID token: openid is required, profile, email, and phone return the matching attributes, and descope.custom_claims and descope.claims return your configured custom claims and the user's tenants, roles, and permissions.

SAML Federated Apps work the same way conceptually but exchange a signed XML assertion rather than a JWT. Configuration has two sides: the Identity Provider section — Descope's SSO URL, Entity ID, and public certificate, which you hand to your SP — and the Service Provider section, the SP's ACS URL, Entity ID, and certificate, which you give to Descope.

## SAML specifics worth knowing

- Metadata URL — the fastest path on either side; if your SP can fetch metadata from a URL, Descope extracts ACS URLs, Entity ID, and certificates automatically.
- SAML Subject / NameID — choose which Descope attribute becomes the subject, and which NameID format the SP expects.
- IdP-Initiated SSO — every SAML app gets a dedicated initiate URL; users hit it directly and are POSTed straight to the SP's ACS URL.
- Single Logout (SLO) — a lightweight logout ending the Descope session and redirecting to a configured URL. It does not propagate logout to other SPs on the same project.

## Key terms

- Federated App — makes Descope the Identity Provider for another application, using OIDC or SAML.
- Application Library — ready-made templates for common federation targets.
- IdP-Initiated SSO — a dedicated URL letting a user reach the SP's ACS URL without an SP-initiated redirect.

![A Federated App's IdP Configuration — the other direction, where Descope is the identity provider and another application is the Service Provider. The Flow dropdown picks which journey its users see. Of the three URLs, Discovery is the one to hand over: an OIDC client reads Issuer, endpoints and keys from it rather than being told each by hand. Supported Claims below controls what the ID token actually carries.](applications-and-federation-outbound-federated-apps)

## Summary

- That no custom auth logic is needed on the Service Provider side
- How an SP self-configures — Discovery URL for OIDC, Metadata URL for SAML
- Which scopes return claims, roles, and tenants
- The Console distinction between Federated Apps and Outbound Apps, which are unrelated features

---
