# OAuth 2.0 Authentication

Configure OAuth 2.0 authentication for Falcon connectors. Supports authorization code flow, client credentials flow, and other oauth 2.0 mechanisms.

---

## Pre-phase — Read Research Data

> Before asking the user anything, read the stored research data. Most questions in Phases 1–3 are already answered there.

### Read the auth research files

```
.connector-dev/{provider}/research/auth/auth_config.yaml
.connector-dev/{provider}/research/auth/scopes.yaml
```

> Only ask follow-up questions for fields that are **missing or ambiguous** in the research data. Do not re-ask for things already confirmed.

**When Pre-phase is complete → proceed to Phase 1.**

---

## Phase 1 — Qualify: Is this OAuth 2.0?

> ⚠️ **CRITICAL:** Analyze the actual authentication flow from the provider's API documentation — do not rely on provider terminology. Providers often call static tokens "OAuth tokens" when no exchange occurs.

**The single defining question: Does obtaining the token require calling an endpoint?**

| Provider uses | Verdict | Skill to use |
|---|---|---|
| Static API key — user copies a key from their account settings | ❌ Not OAuth2 | `auth-custom` |
| Personal Access Token (PAT) — user generates a token manually | ❌ Not OAuth2 | `auth-custom` |
| HTTP Basic Auth — static user + secret sent directly, no exchange step | ❌ Not OAuth2 | `auth-custom` |
| Token obtained by calling an endpoint (`/oauth/token`, `/oauth/authorize/client`, etc.) | ✅ OAuth2 | Continue to Phase 2 |
| Authorization code flow — user authorizes via browser, code exchanged for token | ✅ OAuth2 | Continue to Phase 2 |
| Client credentials — app POSTs `client_id` + `client_secret` to get a token | ✅ OAuth2 | Continue to Phase 2 |

**If the provider uses OAuth2 → proceed to Phase 2.**
**If not → stop and read `references/auth-custom.md`.**

---

## Phase 2 — Collect Common Configuration

### OAuth 2.0 Schema

> **The map key (`oauth2:` below) is the block's identifier, not a fixed keyword** — gates
> A9/B6 match it against the researched `authMechanisms[].type`, so name it after that
> type verbatim (e.g. `- oauth2_bot_token:`).

```yaml
authentication:
  - oauth2:
      type: oauth2
      label: OAuth 2.0                    # Display name in UI
      
      # Optional: Support documentation
      support:
        description: OAuth 2.0 provides secure access...
        guides:
          setup:
            # Setup guide for OAuth app creation
          config:
            # Configuration guide for end users

      # Config fields (T2-facing: end user credentials)
      configFields:
        - key: workspaceId
          label: Workspace ID
          type: text
          required: true
          secret: false
          description: Your workspace identifier

      # Setup fields (T1-facing: multi-tenant credentials) — see setupFields
      # section below for the full schema and field-by-field guidance.
      setupFields:
        # ... (see setupFields section)

      # Choose ONE of the following grantType + authorization blocks:
      # === authorization_code block ===
      grantType: authorization_code
      # For authorization_code, the authorization block includes the full flow
      # config (auth URL, token URL, and params). For client_credentials, it is
      # simpler (no browser redirect, token obtained via app credentials).
      authorization:
        type: oauth2                      # Use built-in OAuth2 flow
        authorizationUrl: https://provider.com/oauth/authorize
        authorizationParams:
          ... # provider-specific params (e.g. scope, response_type)
        tokenUrl: https://provider.com/oauth/token
        tokenParams:
          ... # provider-specific params (grant type, app id, app secret)
        token: $.credentials.accessToken    # JSONPath to the token in response
        includeBearer: true                 # Prefix with "Bearer " when calling API
        refreshUrl: https://provider.com/oauth/token
      # === authorization_code block ===

      # === client_credentials block ===
      grantType: client_credentials
      authorization:
        type: bearer
        token: $.credentials.accessToken
        includeBearer: true
      # === client_credentials block ===

      # Optional: Token refresh configuration
      refreshAuthentication:
        # See "Token Refresh" section below

      # Optional: Post-authentication call
      postAuthentication:
        # See "Post-Authentication" section below

      # Optional: Environment configuration
      environments:
        - key: production
          name: Production

      # Optional: Connection test actions
      testActions:
        - action: list_users
          required: true
```

### Grant Types

grantType: `authorization_code` | `client_credentials`

#### 1. Authorization Code Flow

**Most common**: User authorizes app, receives authorization code, exchanges for access token. **Examples**: Asana, Gmail, Xero, GitHub, Slack

```yaml
# Example Template
authentication:
  - oauth2:
      type: oauth2
      label: OAuth 2.0
      grantType: authorization_code
      configFields:
        ...
      setupFields:
        ...
      authorization:
        type: oauth2
        authorizationUrl: https://provider.com/oauth/authorize
        authorizationParams:
          ...
        tokenUrl: https://provider.com/oauth/token
        tokenParams:
          ...
        token: $.credentials.accessToken
        includeBearer: true
      refreshAuthentication:
        action:
          actionId: refresh_token_{provider}
          categories:
            - internal
          actionType: refresh_token
          label: Refresh Token
          description: Re-authenticate against {Provider} to obtain new credentials
          steps:
            - stepId: fetch_new_credentials
              description: Call the provider re-auth endpoint
              stepFunction:
                ...
            - stepId: map_response
              description: Map response fields into credentials
              stepFunction:
                ...
          result:
            data: $.steps.map_response.output.data
```

#### 2. Client Credentials Flow

**Machine-to-machine**: App authenticates with client credentials, receives access token directly. **Examples**: Greenhouse, GlobalizationPartners, BigChange

```yaml
# Example Template from 360learning
authentication:
  - oauth2:
      type: oauth2
      label: OAuth 2.0
      grantType: client_credentials
      authorization:
        type: bearer
        token: $.credentials.accessToken
        includeBearer: true
      # refreshAuthentication follows the same shape as the Authorization Code
      # flow example above — an internal action that re-fetches credentials
      # using the client-credentials grant. Use a 1800s (30 min) hardcoded
      # expiresIn to keep refresh cadence consistent.
      refreshAuthentication:
        action:
          # ... (see Authorization Code example)
```

### URLs

Gather the following from the provider's API documentation before dispatching to a child skill. These fields are common to all OAuth2 patterns.

| Field | Where to find it | Example |
|-------|------------------|---------|
| `authorizationUrl` | Provider OAuth docs, "Authorization" or "Step 1" | `https://provider.com/oauth/authorize` |
| `authorizationParams` | Necessary parameters to authorize endpoint | `state`, `response_type`, `redirect_uri`, `client_id`, `grant_type` and so on. |
| `tokenUrl` | Provider OAuth docs, "Token" or "Exchange code" | `https://provider.com/oauth/token` |
| `tokenParams` | Necessary parameters to token endpoint | `grant_type`, `client_secret`, `redirect_uri`, `client_id` | 

> **Client credentials only:** No `authorizationUrl` — skip it.
> **Dynamic URLs:** If the URL contains a subdomain or region from user credentials, use interpolation: `https://${credentials.subdomain}.provider.com/oauth/token`. See [connector-patterns/references/schema-vs-corpus.md for interpolation syntax](../../../connector-patterns/references/schema-vs-corpus.md).

### setupFields

Always required for OAuth2. Collected from T1 (platform admin) when registering the OAuth app.

```yaml
setupFields:
  - key: clientId
    label: Client ID
    type: text
    required: true
    secret: false
    placeholder: your-client-id
    description: Client ID from your {Provider} app registration
  - key: clientSecret
    label: Client Secret
    type: password
    required: true
    secret: true
    placeholder: your-client-secret
    description: Client Secret from your {Provider} app registration
  - key: scopes
    label: Scopes
    type: text
    required: false
    secret: false
    placeholder: "{provider default scopes}"
    description: Space-separated list of OAuth scopes to request
  ... # more setupFields will be added here
```

### configFields (when needed)

Add `configFields` only if the connector needs T2 (end user) input beyond the token — such as subdomain, region, workspace ID, or org ID. These are also used for dynamic `baseUrl` construction.

```yaml
configFields:
  - key: subdomain          # example — use the actual field the provider needs
    label: Subdomain
    type: text
    required: true
    secret: false
    placeholder: yourcompany
    description: Your account subdomain (e.g. yourcompany.provider.com)
  ... # more configFields will be added here
```

> Common `configField` types: `text` for subdomain/domain/region, `select` for fixed regions (US/EU/AP), `password` for any sensitive value.

> **Region / Data Center / Server Location:** If provider has geographic regions, add as `configField` or `setupField` (based on research data — who sets the region: T1 admin → setupFields, T2 user → configFields) with `type: select` — NEVER as `environments` entries. Use provider's exact terminology for `label`. See `build-connector-config.md` section 2a for patterns.

### Dynamic baseUrl (when needed)

If the API base URL varies per account/environment (e.g. `sandbox` or `production`), define it at the connector level using credential fields:

```yaml
# Single field
baseUrl: https://${credentials.subdomain}.provider.com

# Multiple fields
baseUrl: https://${credentials.tenant}.${credentials.region}.provider.com/api/v1

# Full URL from user input
baseUrl: ${credentials.apiUrl}/api/v3
```

**When Phase 2 is complete → proceed to Phase 3.**

---

## Phase 3 — Decide & Dispatch to Child Skill

> ⚠️ **CRITICAL:** Answer all 3 diagnostic questions before dispatching. Dispatch to **exactly ONE** child skill. Do not skip this phase or implement `authorization` + `refreshAuthentication` manually.

Answer the following questions using the provider's API token documentation.

---

### Q1: What is the grant type?

| Answer | Action |
|--------|--------|
| `client_credentials` — app POSTs credentials directly, no browser redirect | → **Dispatch `auth-oauth2-client-credentials`**, then skip to Phase 4 |
| `authorization_code` — user authorizes via browser, code exchanged for token | → Continue to Q2 |

---

### Q2 (authorization_code only): Does the initial token response include a `refresh_token`?

Check the provider's token endpoint documentation — look for `refresh_token` in the sample response or in the token response schema.

| Answer | Action |
|--------|--------|
| **No** — response has no refresh field; primary credential is long-lived | → **Dispatch `auth-oauth2-code-longterm`**, then skip to Phase 4 |
| **Yes** — response includes a refresh field | → Continue to Q3 |

> **When unsure:** Check if the docs mention "offline access", "token expiry", or "refresh". If the provider explicitly says tokens do not expire and no refresh token is issued, it is long-lived.

---

### Q3 (has refresh_token): Does the provider require an intermediate exchange step?

An exchange step is needed when the initial `access_token` cannot be used directly — it must first be converted via a **separate endpoint** or **custom `grant_type`** (not the standard `grant_type: refresh_token`).

| Answer | Action |
|--------|--------|
| **No** — standard `grant_type: refresh_token` call to the same token URL | → **Dispatch `auth-oauth2-code-refresh`** |
| **Yes** — provider has a separate exchange endpoint or custom grant type (e.g. `ig_exchange_token`, `fb_exchange_token`, bot token exchange) | → **Dispatch `auth-oauth2-code-exchange`** |

> **Known exchange patterns:** Instagram (`ig_exchange_token`), WhatsApp (`fb_exchange_token`), Slack (bot token exchange), Plaid (public-to-permanent)

---

### Dispatch Table

| grantType            | refresh_token? | Exchange step? | Child Skill                      |
|----------------------|----------------|----------------|----------------------------------|
| `client_credentials` |      —         |        —       | `auth-oauth2-client-credentials` |
| `authorization_code` |      No        |        —       | `auth-oauth2-code-longterm`      |
| `authorization_code` |      Yes       |        No      | `auth-oauth2-code-refresh`       |
| `authorization_code` |      Yes       |        Yes     | `auth-oauth2-code-exchange`      |

---

### Dispatch the Child Skill

Call the identified child skill now:

```
/auth-oauth2-client-credentials {provider}
/auth-oauth2-code-longterm {provider}
/auth-oauth2-code-refresh {provider}
/auth-oauth2-code-exchange {provider}
```

The child skill will produce the `authorization` block and `refreshAuthentication` block. Insert these into the connector YAML under the `oauth2` authentication entry.

> ⚠️ **CRITICAL — Fill in Phase 2 values into the child skill output:**
> The child skill template contains placeholders. Replace them with values collected in Phase 2:
>
> | Placeholder | Replace with |
> |---|---|
> | `authorizationUrl` | URL collected in Phase 2 |
> | `authorizationParams.redirect_uri` | Always `${apiHostUri}/connect/oauth2/{provider}/callback` |
> | `authorizationParams.scope` | `'{{$.credentials.scopes ?? "{provider default scopes}"}}'` |
> | `tokenUrl` | URL collected in Phase 2 |
> | Any extra `authorizationParams` | Provider-specific extras (e.g. `access_type: offline` for Google) |

**When Phase 3 is complete → proceed to Phase 4.**

---

## Phase 4 — Complete Common Configuration

After the child skill returns, complete the remaining fields in the `oauth2` authentication entry.

---

### environments (always required)

Define at minimum a `production` environment. Add `sandbox` only if the provider explicitly offers a separate sandbox API host.

```yaml
environments:
  - key: production
    name: Production
  # Add sandbox only if provider has a separate sandbox host:
  # - key: sandbox
  #   name: Sandbox
```

---

### testActions (always required; can be removed manually if needed)

Choose the simplest action that validates credentials with no required parameters. Prefer a `get` or `list` action that works with minimum scopes.

```yaml
testActions:
  - action: {action_id}   # e.g. list_users, get_current_user, get_account
    required: true
```

**Rules:**
- Use a `LIST` or `GET` action with no required input parameters
- Choose the action that exercises the most restrictive scope the connector uses
- Do not use actions that create or modify data

---

### support guides (always required)

Provide both `setup` (T1 — creating the OAuth app) and `config` (T2 — connecting the account) guides.

#### setup guide

```yaml
support:
  description: OAuth 2.0 provides secure delegated access without sharing credentials.
  guides:
    setup:
      warning: Admin permissions required to create OAuth applications.
      sections:
        - title: Create OAuth application
          content: |
            Register an OAuth application in the {Provider} developer portal.
          steps:
            - title: Access developer settings
              content: |
                Navigate to {Provider}'s developer or API settings.
              list:
                - Sign in to your {Provider} account
                - Go to Settings > Developer > Apps (adjust path per provider)
                - Click "Create new app" or "Register application"
            - title: Configure redirect URL and scopes
              content: |
                Set the callback URL and request the required scopes.
              list:
                - "Redirect URL: https://api.stackone.com/connect/oauth2/{provider}/callback"
                - Select the required scopes for your integration
                - Save the app and copy the Client ID and Client Secret
              displayScopes: true
              applicableScopes: "{space-separated list of scopes required}"
```

#### config guide (when configFields are defined)

Add a `config` guide only if the connector has `configFields` that require T2 user input.

```yaml
    config:
      warning: Keep your credentials secure and do not share them.
      sections:
        - title: Find your {Field Label}
          content: |
            Locate your {field} in {Provider}.
          list:
            - Log in to your {Provider} account
            - Navigate to Settings > {path}
            - Copy the {field} value
```

---

### Final YAML structure check

Before completing, verify the assembled `oauth2` block contains:

| Field | Required | Source |
|-------|----------|--------|
| `type: oauth2` | ✅ Always | Phase 2 |
| `label` | ✅ Always | Phase 2 |
| `grantType` | ✅ Always | Phase 2 / Q1 |
| `support.guides.setup` | ✅ Always | Phase 4 |
| `support.guides.config` | When configFields exist | Phase 4 |
| `configFields` | When needed | Phase 2 |
| `setupFields` | ✅ Always | Phase 2 |
| `authorization` block | ✅ Always | Phase 3 (child) |
| `authorization.authorizationUrl` | ✅ authorization_code only | Phase 2 |
| `authorization.tokenUrl` | ✅ Always | Phase 2 |
| `refreshAuthentication` block | ✅ Always | Phase 3 (child) |
| `environments` | ✅ Always | Phase 4 |
| `testActions` | ✅ Always | Phase 4 |

---

## Critical Rules

### MUST:
- ✅ Always include `refreshAuthentication` — omitting it breaks OAuth2 in Falcon even for long-lived tokens
- ✅ Dispatch to exactly ONE child skill — never implement `authorization` or `refreshAuthentication` manually
- ✅ Mark all sensitive fields with `secret: true` (clientSecret, privateKey, certificates)
- ✅ Use `configFields` (not `setupFields`) for user-specific values like region, subdomain, tenant
- ✅ Always include `testActions` with a simple GET or LIST action
- ✅ Always include `support.guides.setup` with the StackOne redirect URL

### MUST NOT:
- ❌ Use `auth-oauth2` for static API keys or PATs — use `auth-custom` instead
- ❌ Skip Phase 3 — do not choose a child skill based on provider name alone; always verify token response
- ❌ Implement `refreshAuthentication` manually — that is the child skill's responsibility
- ❌ Put user-specific fields (subdomain, region) in `setupFields` — those belong in `configFields`
- ❌ Copy patterns from existing connectors instead of following this skill — existing connectors may use outdated or workaround patterns
- ❌ Add `testActions` with `required: false` — always use `required: true`.

### Source of Truth Rule
**This skill is the primary source of truth for OAuth 2.0 configuration.** Only reference existing connectors in `src/configs/` when this skill is ambiguous or incomplete for a specific case, and only to verify — not to copy wholesale. When in doubt, follow the examples in this skill exactly.

---

## Related Skills

- **`auth-custom`** — API Key, PAT, Basic Auth, Bearer token (non-OAuth)
- **`auth-oauth2-code-longterm`** — authorization_code, no refresh token, long-lived token
- **`auth-oauth2-code-refresh`** — authorization_code, standard refresh token flow
- **`auth-oauth2-code-exchange`** — authorization_code, intermediate exchange step before token is usable
- **`auth-oauth2-client-credentials`** — machine-to-machine, client credentials grant
- **`action-config`** — `authRequirements` field reference (replaces deprecated `requiredScopes`)
- **`connector-patterns/references/authentication.md`** — corpus-mined auth block shapes, setupFields/configFields, and support guide patterns
