INTEGRITY Документация

Аутентификация агентов для написания кода

Coding-агентам, таким как Claude Code, OpenCode и Windsurf, часто требуется доступ к ресурсам, защищенным Cloudflare Access. Если ресурс защищен Access, неаутентифицированные запросы получают перенаправление или 403 ошибка вместо ожидаемого ответа. Вашему агенту нужен способ аутентификации, прежде чем он сможет получить доступ к ресурсу.

На этой странице рассмотрены два метода аутентификации:

Использовать cloudflared

С cloudflared, ваш агент проходит аутентификацию от имени вашей учетной записи пользователя. При первом использовании cloudflared открывает окно браузера для интерактивного входа. После этого сессия сохраняется в течение длительность сессии настроено для приложения. После истечения срока действия сессии для следующего запроса потребуется новый вход через браузер.

Предварительные требования

Скачайте и установите cloudflared.

Выполнять запросы с помощью cloudflared access curl

Для прямых запросов к защищенному ресурсу используйте cloudflared access curl. Это выполняет аутентификацию автоматически и не требует управления токенами.

cloudflared access curl https://example.com/api/endpoint

Если это первый запрос в сеансе, cloudflared открывает браузер для аутентификации пользователя. При необходимости попросите пользователя завершить вход.

Используйте многоразовый токен

Некоторые агенты отправляют HTTP-запросы с помощью собственных клиентских библиотек, а не вызывают cloudflared напрямую. В этом случае войдите в систему, чтобы получить токен, и передайте его в качестве заголовка:

CF_TOKEN=$(cloudflared access login https://example.com)
curl --header "cf-access-token: $CF_TOKEN" https://example.com/api/endpoint

Токен действителен в течение времени сессии, настроенного для приложения.

Подробнее см. в Подключитесь через Access с помощью CLI и cloudflared на стороне клиента.

Использовать токены службы

Токены службы представляют собой статические пары учетных данных, которые аутентифицируют запросы без входа через браузер. Используйте их для автоматизированных рабочих процессов без участия пользователя.

  1. Создать токен службы и сохраните Client ID и Секрет клиента.

  2. В конфигурации политики приложения Access добавьте политика Service Auth. Этот тип политики принимает учетные данные токена сервиса вместо входа через поставщика идентификации. Используйте Service Token селектор и выберите созданный вами токен.

    Действие Тип правила Селектор Значение
    Service Auth Включить Service Token Токен вашего агента
  3. Сохраните Client ID и Client Secret в безопасном месте на вашем компьютере, к которому имеет доступ ваш агент.

  4. Включите оба значения в заголовки запросов к защищённому ресурсу:

    curl --header "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
         --header "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
         https://example.com/api/endpoint

Подробнее см. в Service tokens.

Настройте своего агента

Добавьте AGENTS.md файл в корень вашего проекта со следующим определением skill. Это указывает агентам для написания кода автоматически обнаруживать ресурсы, защищённые Cloudflare Access, и проходить аутентификацию по стандартному потоку OAuth 2.0 с PKCE (RFC 9728).

---
name: access-oauth
description: "Detect Cloudflare Access-protected websites and authenticate via the standard OAuth 2.0 flow (RFC 9728 resource metadata, dynamic client registration, authorization code + PKCE)"
license: MIT
compatibility: opencode
metadata:
  category: authentication
  audience: developers
---

# Access OAuth Authentication

Authenticate to Cloudflare Access-protected resources using standard OAuth 2.0
(resource metadata discovery, dynamic client registration, authorization code with PKCE).

## When to Use

Use this skill when:

- You need to access a URL that returns HTTP 401
- The response contains a `www-authenticate: Bearer` header with a `resource_metadata` URL
- The resource metadata indicates it is a Cloudflare Access-protected resource
- You want to authenticate interactively through the user's IdP

## Step 1: Detect a Protected Resource

Make a request and inspect the response headers:

```bash
curl -sI -L <URL> 2>&1
```

Look for a **401** response with a `www-authenticate` header like:

```
www-authenticate: Bearer realm="OAuth", error="invalid_token",
  error_description="Missing or invalid access token",
  resource_metadata="https://<hostname>/.well-known/cloudflare-access-protected-resource/"
```

If you see this header, the site supports the OAuth flow. Proceed to Step 2.

The JSON body of the 401 will also contain:

```json
{
	"error": "invalid_token",
	"error_description": "Missing or invalid access token",
	"resource_metadata": "https://<hostname>/.well-known/cloudflare-access-protected-resource/"
}
```

### If No `www-authenticate` Header

If the 401 does not include `www-authenticate` with `resource_metadata`, the site may
not support this OAuth flow. Fall back to `cloudflared access curl` or browser-based
authentication.

## Step 2: Fetch Resource Metadata

Fetch the resource metadata URL from the `www-authenticate` header:

```bash
curl -s https://<hostname>/.well-known/cloudflare-access-protected-resource/
```

Expected response:

```json
{
	"resource": "https://<hostname>",
	"protected": true,
	"team_domain": "<team>.cloudflareaccess.com",
	"authorization_servers": ["https://<team>.cloudflareaccess.com"],
	"authentication_method": "cloudflared",
	"authentication_method_description": "Use `cloudflared access curl`...",
	"authentication_method_documentation": "https://developers.cloudflare.com/cloudflare-one/tutorials/cli/"
}
```

Extract the **authorization server** URL from `authorization_servers[0]` (e.g. `https://<team>.cloudflareaccess.com`).

## Step 3: Fetch OAuth Authorization Server Metadata

```bash
curl -s https://<team>.cloudflareaccess.com/.well-known/oauth-authorization-server
```

Expected response:

```json
{
	"issuer": "<team>.cloudflareaccess.com",
	"authorization_endpoint": "https://<team>.cloudflareaccess.com/cdn-cgi/access/oauth/authorization",
	"token_endpoint": "https://<team>.cloudflareaccess.com/cdn-cgi/access/oauth/token",
	"response_types_supported": ["code"],
	"response_modes_supported": ["query"],
	"grant_types_supported": ["authorization_code", "refresh_token"],
	"token_endpoint_auth_methods_supported": [
		"client_secret_basic",
		"client_secret_post",
		"none"
	],
	"revocation_endpoint": "https://<team>.cloudflareaccess.com/cdn-cgi/access/oauth/revoke",
	"registration_endpoint": "https://<team>.cloudflareaccess.com/cdn-cgi/access/oauth/registration",
	"code_challenge_methods_supported": ["S256"]
}
```

Verify that:

- `"none"` is in `token_endpoint_auth_methods_supported` (allows public clients)
- `"authorization_code"` is in `grant_types_supported`
- `"S256"` is in `code_challenge_methods_supported`
- A `registration_endpoint` is present

Extract the **registration_endpoint**, **authorization_endpoint**, and **token_endpoint**.

## Step 4: Dynamic Client Registration

Register a public OAuth client:

```bash
curl -s -X POST <registration_endpoint> \
  -H "Content-Type: application/json" \
  -d '{
    "redirect_uris": ["http://localhost:8400/callback"],
    "token_endpoint_auth_method": "none",
    "grant_types": ["authorization_code"],
    "response_types": ["code"],
    "resource": "https://<hostname>"
  }'
```

Expected response:

```json
{
	"client_id": "<uuid>",
	"redirect_uris": ["http://localhost:8400/callback"],
	"grant_types": ["authorization_code"],
	"response_types": ["code"],
	"token_endpoint_auth_method": "none",
	"registration_client_uri": "...",
	"client_id_issued_at": 1234567890
}
```

Save the **client_id**.

## Step 5: Generate PKCE Challenge

Generate a code verifier and S256 challenge. Ensure the challenge starts with an
alphanumeric character to avoid URL parsing issues:

```bash
while true; do
  CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=' | tr '/+' '_-')
  CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | base64 | tr -d '=' | tr '/+' '_-')
  if [[ "$CODE_CHALLENGE" =~ ^[a-zA-Z0-9] ]]; then
    break
  fi
done
```

**Important**: The code challenge MUST start with `[a-zA-Z0-9]`. A leading `-` or `_`
can cause URL parameter parsing failures on the authorization server.

## Step 6: Authorization Code Flow with Local Callback

Start a local HTTP server to catch the callback, then direct the user to the
authorization URL.

### Build the Authorization URL

```
<authorization_endpoint>?
  client_id=<client_id>&
  redirect_uri=http%3A%2F%2Flocalhost%3A8400%2Fcallback&
  response_type=code&
  code_challenge=<CODE_CHALLENGE>&
  code_challenge_method=S256&
  resource=<URL-encoded target resource>
```

### Start the Callback Listener and Prompt the User

Run a Python HTTP server on port 8400 that captures the authorization code:

```python
python3 -c '
import http.server, urllib.parse

class Handler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        parsed = urllib.parse.urlparse(self.path)
        params = urllib.parse.parse_qs(parsed.query)
        if "code" in params:
            code = params["code"][0]
            with open("/tmp/oauth_code.txt", "w") as f:
                f.write(code)
            self.send_response(200)
            self.send_header("Content-Type", "text/html")
            self.end_headers()
            self.wfile.write(b"<h1>Got it!</h1><p>Authorization code received. You can close this tab.</p>")
            print(f"CODE={code}", flush=True)
        elif "error" in params:
            err = params.get("error", [""])[0]
            desc = params.get("error_description", [""])[0]
            self.send_response(200)
            self.send_header("Content-Type", "text/html")
            self.end_headers()
            self.wfile.write(f"<h1>Error</h1><p>{err}: {desc}</p>".encode())
            print(f"ERROR: {err} - {desc}", flush=True)
        else:
            self.send_response(400)
            self.end_headers()
            self.wfile.write(b"Unexpected request")
            print(f"Unexpected: {self.path}", flush=True)
        import threading
        threading.Thread(target=self.server.shutdown).start()
    def log_message(self, format, *args):
        pass

print("Listening on http://localhost:8400 ...", flush=True)
print("Open the authorization URL in your browser.", flush=True)
http.server.HTTPServer(("", 8400), Handler).serve_forever()
'
```

**Important**: Use a timeout of at least 120000ms for this bash command since the user
needs time to authenticate in the browser.

Tell the user to open the authorization URL in their browser. After they authenticate
with their IdP, the browser will redirect to `http://localhost:8400/callback?code=<code>`,
the server will capture it and shut down.

## Step 7: Exchange Code for Token

```bash
curl -s -X POST <token_endpoint> \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=<AUTH_CODE>" \
  -d "client_id=<CLIENT_ID>" \
  -d "redirect_uri=http://localhost:8400/callback" \
  -d "code_verifier=<CODE_VERIFIER>"
```

Expected response:

```json
{
	"access_token": "oauth:<token>",
	"token_type": "bearer",
	"expires_in": 900,
	"scope": "",
	"resource": "https://<hostname>/",
	"refresh_token": "oauth:<refresh_token>"
}
```

Save the **access_token** and **refresh_token**.

## Step 8: Access the Protected Resource

```bash
curl -s https://<hostname>/ \
  -H "Authorization: Bearer <access_token>"
```

This should now return the actual content behind Cloudflare Access.

## Step 9: Refresh the Token (if needed)

If the access token expires (default 900 seconds), use the refresh token:

```bash
curl -s -X POST <token_endpoint> \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=<REFRESH_TOKEN>" \
  -d "client_id=<CLIENT_ID>"
```

## Quick Reference: Full Flow Summary

```
1. curl -sI <URL>                          # Detect 401 + www-authenticate header
2. curl -s <resource_metadata_url>         # Get authorization server
3. curl -s <as>/.well-known/oauth-authorization-server  # Get endpoints
4. POST <registration_endpoint>            # Register public client
5. Generate PKCE code_verifier + challenge # S256, alphanumeric start
6. Start localhost:8400 listener           # Catch callback
7. User opens authorization URL            # Browser-based IdP auth
8. POST <token_endpoint>                   # Exchange code for token
9. curl -H "Authorization: Bearer <token>" # Access resource
```

## Troubleshooting

| Problem                                                 | Cause                                                               | Fix                                                                        |
| ------------------------------------------------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `code_challenge_method must be S256 for public clients` | Code challenge starts with `-` or `_`, corrupting the URL parameter | Regenerate until challenge starts with `[a-zA-Z0-9]`                       |
| `invalid_grant` on token exchange                       | Code expired or verifier mismatch                                   | Redo the auth flow; codes are single-use and short-lived                   |
| 401 after using token                                   | Token expired (default 15 min)                                      | Use refresh token to get a new access token                                |
| No `www-authenticate` header                            | Site doesn't support OAuth resource metadata                        | Fall back to `cloudflared access curl` or browser auth                     |
| No `registration_endpoint` in AS metadata               | Dynamic registration not enabled                                    | Must use a pre-registered client or different auth method                  |
| Port 8400 already in use                                | Previous listener didn't shut down                                  | Kill the process or use a different port (update redirect_uri accordingly) |