# OIDC Discovery Auditor (`phoenix2810/oidc-discovery-auditor`) Actor

Audit a public OpenID Connect provider's /.well-known/openid-configuration discovery metadata. Check issuer match, required keys, https endpoints, signing algorithms, and PKCE. Returns a score and grade.

- **URL**: https://apify.com/phoenix2810/oidc-discovery-auditor.md
- **Developed by:** [Sanskar Jaiswal](https://apify.com/phoenix2810) (community)
- **Categories:** Developer tools, SEO tools, Open source
- **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

## OIDC Discovery Auditor

Audit a public OpenID Connect provider's `/.well-known/openid-configuration` discovery metadata in one API call. Validates the document against the OpenID Connect Discovery 1.0 specification: required metadata keys, issuer match against the fetched URL, https endpoint URLs, ID token signing algorithms, subject types, and PKCE support. Returns a readiness score, letter grade, per-check analysis, and recommendations. Built for identity engineers, SaaS platform teams, security auditors, and QA pipelines.

### Use cases

- **Identity engineers** - verify discovery metadata before pointing RP/client libraries at a new or upgraded IdP
- **SaaS platform teams** - confirm multi-tenant OIDC providers publish correct issuer values per tenant and conformant endpoint URLs
- **Security auditors and pen-testers** - check for issuer mismatch (clients MUST reject mismatched metadata), unsigned ID token support (`none`), missing https endpoints, and weak PKCE (`plain`)
- **Site migration QA** - catch discovery regressions after domain moves, CDN changes, or IdP upgrades
- **CI/CD pipelines** - schedule conformance checks and alert on score drops

### What it checks

- **Discovery document** - served at `/.well-known/openid-configuration` with HTTP 200 and `application/json` Content-Type
- **JSON validity** - the document body parses as a JSON object
- **Required metadata keys** - `issuer`, `authorization_endpoint`, `jwks_uri`, `response_types_supported` (OIDC Discovery 1.0 section 3)
- **Issuer match** - the declared `issuer` value matches the URL the document was fetched from (OIDC clients MUST reject mismatches); issuer must not contain query or fragment components
- **Endpoint URLs** - every documented endpoint (`authorization_endpoint`, `token_endpoint`, `userinfo_endpoint`, `jwks_uri`, `registration_endpoint`, `end_session_endpoint`, `revocation_endpoint`, `introspection_endpoint`, `pushed_authorization_request_endpoint`, `device_authorization_endpoint`, and more) is a parseable https URL; off-issuer-origin endpoints are flagged as a warning (common on shared IdP platforms)
- **ID token signing algorithms** - `id_token_signing_alg_values_supported` contains recognized JWS algorithms and does not include `none` (prohibited for ID tokens)
- **PKCE support** - `code_challenge_methods_supported` advertises `S256`; `plain` is flagged per RFC 7636
- **Recommended keys** - `token_endpoint`, `id_token_signing_alg_values_supported`, `subject_types_supported` presence; `subject_types_supported` values limited to `public`/`pairwise`

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

### Input

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `startUrl` | string | yes | - | Issuer URL of the OIDC provider (root domain, no path) |
| `timeoutSeconds` | integer | no | `10` | Per-request timeout (3-30 seconds) |

#### Example input

```json
{
  "issuer": "https://accounts.google.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 `/.well-known/openid-configuration` 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 |
| `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 |
| `subjectTypes` | array | null | `subject_types_supported` values |
| `signingAlgs` | array | null | `id_token_signing_alg_values_supported` values |
| `codeChallengeMethods` | array | null | `code_challenge_methods_supported` values (PKCE) |
| `tokenAuthMethods` | array | null | `token_endpoint_auth_methods_supported` values |
| `scopes` | array | null | `scopes_supported` values |
| `grantTypes` | array | null | `grant_types_supported` values |
| `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 | OIDC discovery 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": "https://accounts.google.com",
  "discoveryUrl": "https://accounts.google.com/.well-known/openid-configuration",
  "documentFound": true,
  "jsonValid": true,
  "issuer": "https://accounts.google.com",
  "issuerMatchesFinalUrl": true,
  "missingRequiredKeys": [],
  "signingAlgs": ["RS256"],
  "codeChallengeMethods": ["S256", "plain"],
  "pkceSupported": true,
  "score": 92,
  "grade": "A",
  "checks": [
    {
      "name": "Issuer match",
      "check": "issuer",
      "status": "good",
      "note": "issuer 'https://accounts.google.com' matches the discovery document URL.",
      "weight": 20,
      "recommendation": null
    }
  ],
  "recommendations": []
}
```

### Security

- Public HTTP/HTTPS only; rejects URL credentials, private IP literals (IPv4 and IPv6), private DNS resolutions, and revalidates redirects before following.
- Fetches only the 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 issuer audited | $0.01 |

### FAQ

**Does this fetch the JWKS or test the authorization endpoint?**
No. Only the discovery document is fetched. Endpoint URLs and `jwks_uri` are validated as metadata strings so the actor cannot be used to probe arbitrary URLs.

**Why does issuer mismatch matter?**
OIDC Discovery 1.0 section 3 requires the `issuer` value to be identical to the issuer identifier used to construct the discovery URL. RFC 8414-conformant clients MUST reject metadata where they differ - a mismatch silently breaks client discovery.

**Can I audit an OAuth 2.0 authorization server without OIDC?**
This actor audits the OIDC discovery document. OAuth AS metadata (`/.well-known/oauth-authorization-server`) overlaps heavily but is a separate RFC 8414 layout; only use this actor for OpenID Connect providers.

### Local development

```bash
npm install
npm test        # node --test suite (parsers, SSRF, 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 OpenID Connect provider (issuer root domain, no path). The actor fetches https://<issuer>/.well-known/openid-configuration 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": "https://accounts.google.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": "https://accounts.google.com"
};

// Run the Actor and wait for it to finish
const run = await client.actor("phoenix2810/oidc-discovery-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": "https://accounts.google.com" }

# Run the Actor and wait for it to finish
run = client.actor("phoenix2810/oidc-discovery-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": "https://accounts.google.com"
}' |
apify call phoenix2810/oidc-discovery-auditor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,phoenix2810/oidc-discovery-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/xzSbC1FXoTMJdcIej/builds/G8vgx1nSp1dt9a9hc/openapi.json
