Inbound Apps

Inbound Apps enable your users to sign in to third-party applications using Descope as their identity provider (IdP) via OAuth or OpenID Connect. This allows users to securely authenticate while maintaining control over what data is shared and what actions external applications can perform on their behalf.

Essentially, Inbound Apps allow you to secure your existing APIs with OAuth, making them easier to integrate with AI agents, partner applications, and more. This means:

  • AI-driven applications can authenticate and interact with your APIs using OAuth-based tokens.
  • Partner applications can manage user tokens on behalf of their end users while enforcing consent-based access.
  • Machine-to-machine workflows can securely interact with your API without manual authentication.

By centralizing authentication and consent management, Descope simplifies integration with external services while enhancing security, compliance, and user experience.

Creating an Inbound App

To configure an inbound app in Descope:

  1. Navigate to the Inbound Apps section in the Descope console.
  2. Click + Add Inbound App to create a new inbound application.
  3. Define the App Name and Description (both can be edited later).

Creating an inbound app in Descope

Once the application is created, you can configure its settings, including:

  • App Details
  • Scopes (Permissions & User Data)
  • Connection Information
  • Consent Management

Inbound App Details

This section allows you to configure key information about the inbound app.

  • Logo (Optional): Upload a logo for the application. This will be displayed during user consent flows.
  • Inbound App Name (Required): The name of the third-party application.
  • Description (Optional): A short description of the app.
  • App ID (System Generated): A unique identifier for the application (cannot be changed).

Configuring an inbound app's details in Descope

Scopes

Scopes define what permissions and user data the third-party application can access.

Configuring an inbound app's scopes in Descope

Permission Scopes

Permission scopes allow the inbound app to perform specific actions on behalf of the user or tenant. If using Descope's RBAC model, these scopes are mapped to roles.

  • Name (Required): The identifier for the permission scope.
  • Description (Required): A summary of the permission.
  • Roles (Optional): Assign roles if RBAC is enabled.

User Information Scopes

These scopes define what user data is shared with the third-party application. You can include built-in Descope attributes or custom attributes.

  • Name (Required): The identifier for the user data scope.
  • Description (Required): A summary of the data being shared.
  • User Attribute (Required): The specific user attribute mapped to this scope. This utilizes Descope's role-based authorization (RBAC) model to control access.

Connection Information

This section contains configuration details needed to integrate the inbound app with Descope.

Note

If you have a custom domain configured, the system-generated URLs will use your custom CNAME instead of api.descope.com.

  • Flow Hosting URL (Required): The URL where the consent flow is hosted. It can be:
    • api.descope.com
    • Your custom CNAME
    • A self-hosted instance of the consent flow.
  • Approved Callback URLs (Optional): The URLs Descope is allowed to redirect users to after authentication.
  • Client ID (System Generated): The unique identifier for the application.
  • Client Secret (System Generated): The shared secret used for authentication.
  • Discovery URL (System Generated): Provides OpenID Connect configuration details, including supported scopes, public keys, and endpoints.
  • Issuer (System Generated): The issuer URL used for verifying identity tokens.
  • Authorization URL (System Generated): The endpoint for initiating authentication (https://api.descope.com/oauth2/v1/apps/authorize).
  • Access Token URL (System Generated): The endpoint for retrieving access tokens (https://api.descope.com/oauth2/v1/apps/token).

Configuring an inbound app's connection information in Descope

You will not be able to create an inbound app token, without providing consent in your flow defined in your Flow Hosting URL. This will result in an error if you do not possess the consent screen and action within your flow.

The Consent tab provides visibility into which users have authorized the inbound app and what permissions they granted.

  • Consent ID: A unique identifier for the user's consent.
  • Scopes: The granted permissions.
  • Associated User: The ID of the user who granted consent.
  • Associated Tenant: The tenant ID, if applicable.
  • Granting User: The user who authorized the app (e.g., an admin granting consent for a team).
  • Creation Time: Timestamp when consent was given.

Viewing inbound app consents in Descope

Authenticating Users with an Inbound App

Once an inbound app is configured, users can sign in using Descope as the identity provider.

Initiating Login with Descope as an IdP

The third-party application should redirect users to Descope's Authorization URL:

https://api.descope.com/oauth2/v1/apps/authorize? 
  client_id=<CLIENT_ID>
  &redirect_uri=https://yourapp.com/callback
  &response_type=code
  &scope=openid email profile
  &state=<RANDOM_STATE_STRING>

This prompts the user to sign in and approve the requested scopes.

Handling the OAuth Callback

After successful authentication, Descope redirects the user back to the specified redirect_uri with an authorization code. The app can then exchange this code for an access token.

Example: Exchanging the Authorization Code for an Access Token

You can also use PKCE for enhanced security, or for OAuth 2.1 based authentication flows that mandate the use of PKCE.

curl -X POST "https://api.descope.com/oauth2/v1/apps/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "client_id=<CLIENT_ID>" \
  -d "client_secret=<CLIENT_SECRET>" \
  -d "code=<AUTHORIZATION_CODE>" \
  -d "redirect_uri=https://yourapp.com/callback"

Response Example

{
  "access_token": "eyJhbGciOiJIUz...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "abc123",
  "id_token": "eyJhbGciOiJIUz..."
}

Once authenticated, you can use the access token to call the Descope protected APIs.

Developing APIs to Support OAuth Scopes & Permissions

To fully leverage Inbound Apps, your APIs should be designed to enforce OAuth scopes and permissions effectively. This ensures secure and granular access control, allowing AI agents, partner applications, and users to interact with your APIs while respecting consented permissions.

1. Enforce Scope-Based Access Control

Scopes define what an application can do on behalf of a user. When an inbound app requests an access token, the API should verify that the token includes the required scopes for the requested operation.

Example: Validating Scopes in an API Request

Python (FastAPI example):

from fastapi import FastAPI, Depends, HTTPException, Header
from descope import DescopeClient
from descope.exceptions import DescopeException
 
# Initialize Descope client
DESCOPE_PROJECT_ID = "your-project-id"
descope_client = DescopeClient(project_id=DESCOPE_PROJECT_ID)
 
app = FastAPI()
 
# Function to validate session and extract scopes
def get_token_scopes(authorization: str = Header(None)):
    if not authorization or not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Missing or invalid Authorization header")
 
    token = authorization.split("Bearer ")[1]
    try:
        # Validate the session with Descope
        session_data = descope_client.validate_session(token)
        return session_data.get("claims", {}).get("scope", "").split()
    except DescopeException:
        raise HTTPException(status_code=401, detail="Invalid or expired token")
 
# Function to enforce required scopes
def require_scope(required_scope: str):
    def check_scope(token_scopes: list = Depends(get_token_scopes)):
        if required_scope not in token_scopes:
            raise HTTPException(status_code=403, detail="Insufficient permissions")
    return check_scope
 
# Protected API endpoint
@app.get("/contacts", dependencies=[Depends(require_scope("contacts.read"))])
async def get_contacts():
    return {"message": "Returning user's contacts"}

This ensures that only tokens with the contacts.read scope can call the /contacts API.

2. Use Role-Based Access Control (RBAC) with OAuth Scopes

Descope allows mapping RBAC roles to scopes, ensuring that permissions align with organizational policies.

Example: Mapping Roles to Scopes in API Requests

If a user has an editor role in Descope, your API might grant them the ability to modify content:

{
  "sub": "user123",
  "scope": "contacts.read contacts.write",
  "roles": ["editor"]
}

Your backend should check for both scopes and roles when authorizing requests.

3. Support Machine-to-Machine (M2M) Access

If your API is consumed by automated services (e.g., AI agents or partner apps), design endpoints that accept OAuth client credentials instead of user-based authentication.

Example: Client Credentials Grant Flow API Authentication

curl -X POST "https://api.descope.com/oauth/token" \
  -d "grant_type=client_credentials" \
  -d "client_id=your-client-id" \
  -d "client_secret=your-client-secret"

The returned access token should only include scopes granted to the client application.

Learn more about client credentials flow in the OAuth 2.0 specification.

4. Implement Fine-Grained Data Filtering

Instead of granting blanket access to all user data, return only what's allowed based on scopes.

Example: Conditional Data Filtering Based on Scopes

@app.get("/files")
async def list_files(token_scopes: list = Depends(get_token_scopes)):
    files = [{"id": 1, "name": "Private Doc", "shared": False}]
    
    # If the token lacks 'files.read_private', exclude private files
    if "files.read_private" not in token_scopes:
        files = [f for f in files if f["shared"]]
 
    return files

This ensures that users or applications with only files.read_public cannot access private data.

Building APIs with OAuth in mind makes them more secure, flexible, and future-proof for evolving authentication and authorization needs.

Was this helpful?

On this page