# OAuth Authorization Server Auditor (`phoenix2810/oauth-authorization-server-auditor`) Actor

Audit a public OAuth 2.0 authorization server's RFC 8414 discovery metadata in one API call. Checks issuer match, required keys, endpoints, and PKCE support.

- **URL**: https://apify.com/phoenix2810/oauth-authorization-server-auditor.md
- **Developed by:** [Sanskar Jaiswal](https://apify.com/phoenix2810) (community)
- **Categories:** Developer tools, SEO tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## OAuth Authorization Server Auditor

Audit a public OAuth 2.0 authorization server's `/.well-known/oauth-authorization-server` discovery metadata in one API call. Validates the document against RFC 8414 (OAuth 2.0 Authorization Server Metadata): well-known URI construction, required metadata keys (conditional on supported grant types), issuer match against the fetched URL, https endpoint URLs, and PKCE support. Returns a readiness score, letter grade, per-check analysis, and recommendations. Built for API platform teams, OAuth/SSO infrastructure engineers, security auditors, and CI/CD pipelines.

This actor targets plain OAuth 2.0 authorization servers defined by RFC 8414 — API platforms, developer-tool OAuth providers, and authorization servers that are not full OpenID Connect identity providers. It does not audit OpenID Connect Discovery documents (`/.well-known/openid-configuration`); use a dedicated OIDC discovery auditor for that.

### Use cases

- **API platform teams** - verify RFC 8414 metadata before publishing a new or upgraded OAuth 2.0 authorization server
- **OAuth/SSO infrastructure engineers** - confirm endpoint URLs, issuer value, and grant-type-conditional required fields are published correctly
- **Security auditors** - check for issuer mismatch (clients MUST reject mismatched metadata), missing https endpoints, and weak PKCE (`plain`)
- **Devtool integrators** - validate a third-party OAuth authorization server's metadata before wiring client libraries against it
- **CI/CD pipelines** - run automated conformance checks after OAuth authorization server configuration changes and alert on score drops

### What it checks

- **Discovery document** - served at `/.well-known/oauth-authorization-server` with HTTP 200 and `application/json` Content-Type
- **JSON validity** - the document body parses as a JSON object
- **Well-known URI construction (RFC 8414 section 3.1)** - correctly inserts the well-known path segment BEFORE any issuer path component (`https://example.com/.well-known/oauth-authorization-server/issuer1`), unlike OIDC Discovery which appends after the path
- **Required metadata keys** - `issuer` is always required; `authorization_endpoint` and `token_endpoint` are required unless `grant_types_supported` indicates the server does not need them (e.g. a `client_credentials`-only server does not need `authorization_endpoint`); `response_types_supported` is required when `authorization_endpoint` is present
- **Issuer match** - the declared `issuer` value matches the URL used to construct the discovery document request (RFC 8414 section 3.3); issuer must not contain query or fragment components and must be https
- **Endpoint URLs** - every documented endpoint (`authorization_endpoint`, `token_endpoint`, `jwks_uri`, `registration_endpoint`, `revocation_endpoint`, `introspection_endpoint`) is a parseable https URL; off-issuer-origin endpoints are flagged as a warning (common on shared platforms)
- **PKCE support** - `code_challenge_methods_supported` advertises `S256`; `plain` alone is flagged per RFC 7636
- **Recommended keys** - `jwks_uri`, `grant_types_supported`, `token_endpoint_auth_methods_supported`, `token_endpoint_auth_signing_alg_values_supported`, `revocation_endpoint` (+ auth methods), `introspection_endpoint` (+ auth methods), `code_challenge_methods_supported`, `service_documentation`, `ui_locales_supported`, `op_policy_uri`, `op_tos_uri`, `scopes_supported`

Endpoint URLs found inside the metadata are **never fetched** - only the single discovery document on the provided issuer host is read.

### Input

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `startUrl` | string | yes | - | Issuer URL of the OAuth 2.0 authorization server (domain, optionally with a path for multi-tenant issuers) |
| `timeoutSeconds` | integer | no | `10` | Per-request timeout (3-30 seconds) |

#### Example input

```json
{
  "startUrl": "github.com",
  "timeoutSeconds": 10
}
```

### Output

A single dataset item with the full audit:

| Field | Type | Description |
|---|---|---|
| `inputIssuer` | string | The issuer URL provided as input |
| `discoveryUrl` | string | The RFC 8414 `/.well-known/oauth-authorization-server` URL that was fetched |
| `finalUrl` | string | Final URL after redirects |
| `https` | boolean | Whether the discovery document was served over HTTPS |
| `status` | integer | Final HTTP status code |
| `documentFound` | boolean | Whether a 200 discovery document was served |
| `jsonValid` | boolean | Whether the document body is valid JSON |
| `parseError` | string | null | JSON parse error message, or null |
| `issuer` | string | null | Declared `issuer` value in the metadata |
| `issuerMatchesFinalUrl` | boolean | Whether the declared issuer matches the fetched URL |
| `requiredKeys` | array | Required metadata keys present for this document (conditional on grant types) |
| `missingRequiredKeys` | array | Required metadata keys missing |
| `missingRecommendedKeys` | array | Recommended metadata keys missing |
| `metadataKeyCount` | integer | Total metadata keys in the document |
| `endpoints` | array | Per-endpoint analysis (`key`, `url`, `https`, `parseable`, `issuerHost`, `issues`) |
| `responseTypes` | array | null | `response_types_supported` values |
| `grantTypes` | array | null | `grant_types_supported` values |
| `scopes` | array | null | `scopes_supported` values |
| `codeChallengeMethods` | array | null | `code_challenge_methods_supported` values (PKCE) |
| `tokenAuthMethods` | array | null | `token_endpoint_auth_methods_supported` values |
| `tokenAuthSigningAlgs` | array | null | `token_endpoint_auth_signing_alg_values_supported` values |
| `revocationAuthMethods` | array | null | `revocation_endpoint_auth_methods_supported` values |
| `introspectionAuthMethods` | array | null | `introspection_endpoint_auth_methods_supported` values |
| `uiLocales` | array | null | `ui_locales_supported` values |
| `serviceDocumentation` | string | null | `service_documentation` URL |
| `opPolicyUri` | string | null | `op_policy_uri` URL |
| `opTosUri` | string | null | `op_tos_uri` URL |
| `pkceSupported` | boolean | Whether PKCE code challenge methods are advertised |
| `checks` | array | Per-check analysis (name, status, note, weight, recommendation) |
| `issues` | array | Aggregated issue descriptions |
| `score` | integer | OAuth authorization server metadata readiness score (0-100) |
| `grade` | string | Letter grade (A+, A, B, C, D, E, F) |
| `checkedAt` | string | ISO 8601 timestamp |
| `recommendations` | array | Actionable recommendations |

#### Example output (abridged)

```json
{
  "inputIssuer": "github.com",
  "discoveryUrl": "https://github.com/.well-known/oauth-authorization-server",
  "documentFound": true,
  "jsonValid": true,
  "issuer": "https://github.com",
  "issuerMatchesFinalUrl": true,
  "missingRequiredKeys": [],
  "grantTypes": ["authorization_code"],
  "codeChallengeMethods": ["S256"],
  "pkceSupported": true,
  "score": 90,
  "grade": "A",
  "checks": [
    {
      "name": "Issuer match",
      "check": "issuer",
      "status": "good",
      "note": "issuer 'https://github.com' matches the discovery document URL.",
      "weight": 20,
      "recommendation": null
    }
  ],
  "recommendations": []
}
```

### Security

- Public HTTP/HTTPS only; rejects URL credentials, localhost and private/link-local IPv4 and IPv6 literal ranges (RFC 1918, RFC 3927, and IPv6 unique-local/link-local equivalents, including bracketed IPv6 literals), and DNS resolutions to private IP ranges.
- Redirects are followed manually (up to 3 hops) and each hop is revalidated against the same SSRF checks before following.
- Fetches only the single `/.well-known/oauth-authorization-server` discovery document on the provided issuer host; endpoint URLs found inside the metadata are never fetched.
- Bounded body read (2 MB cap); no login, no JavaScript execution, no cookies, no stored page content.

### Pricing

Pay-per-event: one scored audit per run (~$0.015/run).

| Event | Price |
|---|---|
| Actor start | $0.005 |
| Per domain audited | $0.01 |

### FAQ

**Does this fetch the JWKS or probe any OAuth endpoint?**
No. Only the discovery document is fetched. Endpoint URLs (`authorization_endpoint`, `token_endpoint`, `jwks_uri`, etc.) are validated as metadata strings so the actor cannot be used to probe arbitrary URLs.

**Why does the well-known URL differ from OIDC Discovery?**
RFC 8414 section 3.1 inserts the `/.well-known/oauth-authorization-server` path segment before any issuer path component (`https://example.com/.well-known/oauth-authorization-server/issuer1`). OpenID Connect Discovery instead appends the well-known segment after the issuer path. This actor implements the RFC 8414 construction rule; it is not interchangeable with an OIDC discovery auditor.

**Why does issuer mismatch matter?**
RFC 8414 section 3.3 requires the `issuer` value returned in the metadata to be identical to the issuer identifier used to construct the discovery document request URL. Conformant clients MUST reject metadata where they differ - a mismatch silently breaks client discovery and can be a sign of misconfiguration or a man-in-the-middle condition.

**Does this work for authorization servers that only support `client_credentials`?**
Yes. `authorization_endpoint` is only treated as required when `grant_types_supported` indicates a grant type that uses it (`authorization_code` or `implicit`), or when `grant_types_supported` is absent (RFC 8414 defaults to `authorization_code` support). A `client_credentials`-only server correctly scores without `authorization_endpoint`.

**Can I use this to audit a full OpenID Connect provider?**
This actor audits plain OAuth 2.0 authorization server metadata per RFC 8414. OpenID Connect providers publish a related but distinct discovery document (`/.well-known/openid-configuration`) with its own well-known URI construction rule; use a dedicated OIDC discovery auditor for those.

### Local development

```bash
npm install
npm test        # node --test suite (parsers, SSRF, well-known URI construction, scoring, live smoke)
npm run lint    # node --check
python3 ../../scripts/audit_actor.py .   # portfolio security audit (run from actors/ dir)
```

# Actor input Schema

## `startUrl` (type: `string`):

Public issuer identifier of the OAuth 2.0 authorization server (domain, optionally with a path for multi-tenant issuers). The actor fetches the RFC 8414 well-known discovery document (inserting /.well-known/oauth-authorization-server before any issuer path) and audits the metadata. HTTP and HTTPS only. Private IP ranges are blocked.

## `timeoutSeconds` (type: `integer`):

Timeout for the HTTP request.

## Actor input object example

```json
{
  "startUrl": "github.com",
  "timeoutSeconds": 10
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrl": "github.com"
};

// Run the Actor and wait for it to finish
const run = await client.actor("phoenix2810/oauth-authorization-server-auditor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "startUrl": "github.com" }

# Run the Actor and wait for it to finish
run = client.actor("phoenix2810/oauth-authorization-server-auditor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrl": "github.com"
}' |
apify call phoenix2810/oauth-authorization-server-auditor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,phoenix2810/oauth-authorization-server-auditor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/r2Y0hl6aJ5ToFgffM/builds/MQ0GCR07Fe9a1YGkp/openapi.json
