Skip to content

Bearer Token Authentication #1260

@pashifika

Description

@pashifika

Preflight checklist

Ory Network Project

No response

Describe the bug

OAuth2 Bearer Token authentication (issued by Hydra) does not work correctly in Oathkeeper v25.4.0.
Both the bearer_token and oauth2_introspection authenticators have issues that prevent the API client flow from functioning.

Reproducing the bug

Affected Flow

API Client → Oathkeeper(:4455) → app-user(:8082) / app-admin(:8081)
                ↓
        Bearer Token validation fails

Environment

  • Oathkeeper: oryd/oathkeeper:v25.4.0
  • Hydra: oryd/hydra:v25.4.0
  • Platform: macOS (Darwin) ARM64 (Apple Silicon)
  • Docker: Docker Desktop for Mac (ARM64)

bearer_token authenticator — Incompatible with Hydra Introspect

Symptom

curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:4455/user/me
# → {"error":{"code":401,"status":"Unauthorized","message":"Access credentials are invalid"}}

Root Cause

The bearer_token authenticator forwards the token to check_session_url as an Authorization header.
However, Hydra's introspect endpoint (RFC 7662) expects the token in the POST body as token=<value>.

Verification (executed from within the container)

# What bearer_token does (forwards via Authorization header)
POST http://hydra:4445/admin/oauth2/introspect
Authorization: Bearer <token>
# → 400 Bad Request ❌

# Correct introspect call (token in POST body)
POST http://hydra:4445/admin/oauth2/introspect
Content-Type: application/x-www-form-urlencoded
Body: token=<token>
# → {"active": true, ...} ✅

Configuration (does not work)

# oathkeeper.yml
authenticators:
  bearer_token:
    enabled: true
    config:
      check_session_url: http://hydra:4445/admin/oauth2/introspect
      preserve_path: true
      token_from:
        header: Authorization
      force_method: POST
      extra_from: "@this"
      subject_from: "sub"

Conclusion

The bearer_token authenticator is fundamentally incompatible with Hydra's introspect endpoint.
This handler is designed for session stores that accept Authorization headers, such as Kratos' sessions/whoami.

Relevant log output

Relevant configuration

Version

v25.4.0

On which operating system are you observing this issue?

None

In which environment are you deploying?

None

Additional Context

oauth2_introspection authenticator — OOM Crash on ARM64

Symptom

Enabling oauth2_introspection causes Oathkeeper to crash immediately after startup, entering a restart loop.

oathkeeper  | fatal error: runtime: out of memory

The stack trace points to the github.com/dgraph-io/ristretto cache library.

Root Cause

The ristretto cache library (v1.0.0) used internally by oauth2_introspection fails to allocate memory on ARM64, causing an OOM crash.

The following mitigations were attempted, all without success:

  • Explicitly setting cache.enabled: false → OOM occurs during ristretto initialization itself
  • Setting Docker mem_limit: 512m → Same crash
  • Setting GOMAXPROCS: 2 → Same crash

Configuration (crashes)

# oathkeeper.yml
authenticators:
  oauth2_introspection:
    enabled: true
    config:
      introspection_url: http://hydra:4445/admin/oauth2/introspect

Note: Schema Validation Errors

Including subject_from or token_from in the config results in schema validation rejection.

additionalProperties "subject_from" not allowed

Even with a minimal config (introspection_url only), the cache library OOM crash occurs.

Conclusion

oauth2_introspection is unusable on v25.4.0 ARM64.
This is presumed to be an ARM64 compatibility issue in the ristretto library.


Workaround: jwt authenticator + Hydra JWT Access Token Strategy

Approach

Configure Hydra to issue access tokens in JWT format, and use Oathkeeper's jwt authenticator to verify the signature locally via JWKS.
This bypasses the introspect endpoint entirely, avoiding both issues above.

Verified Working

Changes Required

1. hydra.full.yml — Enable JWT access token strategy

strategies:
  access_token: jwt

2. oathkeeper.yml — Add jwt authenticator

authenticators:
  bearer_token:
    enabled: false
  jwt:
    enabled: true
    config:
      jwks_urls:
        - http://hydra:4444/.well-known/jwks.json
      scope_strategy: wildcard
      trusted_issuers:
        - http://hydra:4444  # Must match the issuer configuration

3. rules.yml — Update protected route authenticators

# admin-api, user-api authenticators
authenticators:
  - handler: cookie_session
  - handler: jwt  # Changed from bearer_token to jwt

Verification Steps

# 1. Obtain a JWT token
TOKEN=$(curl -s -X POST http://127.0.0.1:4455/oauth2/token \
  -u "<client_id>:<client_secret>" \
  -d "grant_type=client_credentials&scope=openid" | jq -r .access_token)

# 2. Confirm the token is in JWT format (starts with ey...)
echo $TOKEN

# 3. Access the API
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:4455/user/me
# → 200 OK ✅

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is not working.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions