← Cloudflare One / cloudflare-one / access-controls / applications / http-apps / authorization-cookie
Проверить JWT
Когда Cloudflare отправляет запрос к вашему источнику, в этот запрос включается токен приложения как Cf-Access-Jwt-Assertion заголовок запроса. Запросы, отправленные через браузер, также будут передавать токен как CF_Authorization cookie.
Cloudflare подписывает токен парой ключей, уникальной для вашего аккаунта. Проверяйте токен своим открытым ключом, чтобы убедиться, что запрос пришёл от Access, а не от злоумышленника. Рекомендуем проверять Cf-Access-Jwt-Assertion заголовок вместо CF_Authorization файл cookie, поскольку нет гарантии, что этот файл cookie будет передан.
Ключи подписи Access
Публичный ключ пары ключей подписи находится по адресу https://<your-team-name>.cloudflareaccess.com/cdn-cgi/access/certs, где <your-team-name> это ваш Cloudflare One название команды.
По умолчанию Access меняет ключ подписи каждые 6 недель. Это значит, что вам нужно будет обновлять ключи программно или вручную при каждой смене. Предыдущие ключи остаются действительными в течение 7 дней после смены, чтобы у вас было время внести изменения.
Вы также можете вручную обновить ключ с помощью API. Это можно сделать в целях тестирования или обеспечения безопасности.
Как показано в примере ниже, https://<your-team-name>.cloudflareaccess.com/cdn-cgi/access/certs содержит два открытых ключа: текущий ключ, который используется для подписи всех новых токенов, и предыдущий ключ, который был заменен.
keys: оба ключа в формате JWKpublic_cert: текущий ключ в формате PEMpublic_certs: оба ключа в формате PEM
{
"keys": [
{
"kid": "1a1c3986a44ce6390be42ec772b031df8f433fdc71716db821dc0c39af3bce49",
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"e": "AQAB",
"n": "5PKw-...-AG7MyQ"
},
{
"kid": "6c3bffef71bb0a90c9cbef3b7c0d4a1c7b4b8b76b80292a623afd9dac45d1c65",
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"e": "AQAB",
"n": "pwVn...AA6Hw"
}
],
"public_cert": {
"kid": "6c3bffef71bb0a90c9cbef3b7c0d4a1c7b4b8b76b80292a623afd9dac45d1c65",
"cert": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- "
},
"public_certs": [
{
"kid": "1a1c3986a44ce6390be42ec772b031df8f433fdc71716db821dc0c39af3bce49",
"cert": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- "
},
{
"kid": "6c3bffef71bb0a90c9cbef3b7c0d4a1c7b4b8b76b80292a623afd9dac45d1c65",
"cert": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- "
}
]
}Проверить JWT вручную
Чтобы проверить токен вручную:
-
Скопируйте JWT из
Cf-Access-Jwt-Assertionзаголовок запроса. -
Перейдите в jwt.io ↗.
-
Выберите алгоритм RS256.
-
Вставьте JWT в Закодировано поле.
-
В Полезная нагрузка поле убедитесь, что
issполе указывает на домен вашей команды (https://<your-team-name>.cloudflareaccess.com).jwt.ioиспользуетissзначение для получения открытого ключа для проверки токена. -
Убедитесь, что на странице отображается Signature Verified.
Теперь вы можете быть уверены, что этот запрос отправлен именно Access.
Программная проверка
На исходном сервере можно запустить автоматизированный скрипт для проверки входящих запросов. Предоставленный пример кода получает токен приложения из запроса и проверяет его подпись по вашему открытому ключу. В пример кода нужно будет вставить домен вашей команды и тег Application Audience (AUD).
Получение тега AUD
Cloudflare Access присваивает каждому приложению уникальный тег AUD. aud утверждение в теле токена указывает, для какого приложения действителен JWT.
Чтобы получить тег AUD:
- В Панель управления Cloudflare ↗, перейдите в Zero Trust > Контроль доступа > Приложения.
- Выберите Настройте для вашего приложения.
- От Дополнительные настройки, скопируйте Application Audience (AUD) Tag.
Теперь тег AUD можно вставить в скрипт проверки токена. Тег AUD не изменится, пока вы не удалите или не пересоздадите приложение Access.
Пример Cloudflare Workers
Когда Cloudflare Access находится перед вашим Worker, вашему Worker все равно нужно проверять JWT, который Cloudflare Access добавляет в Cf-Access-Jwt-Assertion заголовок во входящем запросе.
Следующий код проверяет JWT с помощью NPM-пакет jose ↗:
import { jwtVerify, createRemoteJWKSet } from "jose";
export default {
async fetch(request, env, ctx) {
// Verify the POLICY_AUD environment variable is set
if (!env.POLICY_AUD) {
return new Response("Missing required audience", {
status: 403,
headers: { "Content-Type": "text/plain" },
});
}
// Get the JWT from the request headers
const token = request.headers.get("cf-access-jwt-assertion");
// Check if token exists
if (!token) {
return new Response("Missing required CF Access JWT", {
status: 403,
headers: { "Content-Type": "text/plain" },
});
}
try {
// Create JWKS from your team domain
const JWKS = createRemoteJWKSet(
new URL(`${env.TEAM_DOMAIN}/cdn-cgi/access/certs`),
);
// Verify the JWT
const { payload } = await jwtVerify(token, JWKS, {
issuer: env.TEAM_DOMAIN,
audience: env.POLICY_AUD,
});
// Token is valid, proceed with your application logic
return new Response(`Hello ${payload.email || "authenticated user"}!`, {
headers: { "Content-Type": "text/plain" },
});
} catch (error) {
// Token verification failed
const message = error instanceof Error ? error.message : "Unknown error";
return new Response(`Invalid token: ${message}`, {
status: 403,
headers: { "Content-Type": "text/plain" },
});
}
},
};import { jwtVerify, createRemoteJWKSet } from "jose";
interface Env {
POLICY_AUD: string;
TEAM_DOMAIN: string;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
// Verify the POLICY_AUD environment variable is set
if (!env.POLICY_AUD) {
return new Response("Missing required audience", {
status: 403,
headers: { "Content-Type": "text/plain" },
});
}
// Get the JWT from the request headers
const token = request.headers.get("cf-access-jwt-assertion");
// Check if token exists
if (!token) {
return new Response("Missing required CF Access JWT", {
status: 403,
headers: { "Content-Type": "text/plain" },
});
}
try {
// Create JWKS from your team domain
const JWKS = createRemoteJWKSet(
new URL(`${env.TEAM_DOMAIN}/cdn-cgi/access/certs`)
);
// Verify the JWT
const { payload } = await jwtVerify(token, JWKS, {
issuer: env.TEAM_DOMAIN,
audience: env.POLICY_AUD,
});
// Token is valid, proceed with your application logic
return new Response(
`Hello ${payload.email || "authenticated user"}!`,
{
headers: { "Content-Type": "text/plain" },
}
);
} catch (error) {
// Token verification failed
const message = error instanceof Error ? error.message : "Unknown error";
return new Response(`Invalid token: ${message}`, {
status: 403,
headers: { "Content-Type": "text/plain" },
});
}
},
};Необходимые переменные окружения
Добавьте эти переменные окружения в ваш Worker:
POLICY_AUD: вашего приложения Тег AUDTEAM_DOMAIN:https://<your-team-name>.cloudflareaccess.com, где<your-team-name>заменяется вашим фактическим имя команды.
Эти переменные можно задать, добавив их в файл Worker конфигурационный файл Wrangler, или через панель управления Cloudflare в разделе Workers & Pages > your-worker > Настройки > Переменные окружения.
Пример на Golang
package main
import (
"context"
"fmt"
"net/http"
"github.com/coreos/go-oidc/v3/oidc"
)
var (
ctx = context.TODO()
teamDomain = "https://test.cloudflareaccess.com"
certsURL = fmt.Sprintf("%s/cdn-cgi/access/certs", teamDomain)
// The Application Audience (AUD) tag for your application
policyAUD = "4714c1358e65fe4b408ad6d432a5f878f08194bdb4752441fd56faefa9b2b6f2"
config = &oidc.Config{
ClientID: policyAUD,
}
keySet = oidc.NewRemoteKeySet(ctx, certsURL)
verifier = oidc.NewVerifier(teamDomain, keySet, config)
)
// VerifyToken is a middleware to verify a CF Access token
func VerifyToken(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
headers := r.Header
// Make sure that the incoming request has our token header
// Could also look in the cookies for CF_AUTHORIZATION
accessJWT := headers.Get("Cf-Access-Jwt-Assertion")
if accessJWT == "" {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("No token on the request"))
return
}
// Verify the access token
ctx := r.Context()
_, err := verifier.Verify(ctx, accessJWT)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(fmt.Sprintf("Invalid token: %s", err.Error())))
return
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func MainHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("welcome"))
})
}
func main() {
http.Handle("/", VerifyToken(MainHandler()))
http.ListenAndServe(":3000", nil)
}Пример на Python
pip установите следующее:
- flask
- запросы
- PyJWT
- криптография
from flask import Flask, request
import requests
import jwt
import json
import os
app = Flask(__name__)
# The Application Audience (AUD) tag for your application
POLICY_AUD = os.getenv("POLICY_AUD")
# Your CF Access team domain
TEAM_DOMAIN = os.getenv("TEAM_DOMAIN")
CERTS_URL = "{}/cdn-cgi/access/certs".format(TEAM_DOMAIN)
def _get_public_keys():
"""
Returns:
List of RSA public keys usable by PyJWT.
"""
r = requests.get(CERTS_URL)
public_keys = []
jwk_set = r.json()
for key_dict in jwk_set['keys']:
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key_dict))
public_keys.append(public_key)
return public_keys
def verify_token(f):
"""
Decorator that wraps a Flask API call to verify the CF Access JWT
"""
def wrapper():
# Check for the POLICY_AUD environment variable
if not POLICY_AUD:
return "missing required audience", 403
token = ''
if 'CF_Authorization' in request.cookies:
token = request.cookies['CF_Authorization']
else:
return "missing required cf authorization token", 403
keys = _get_public_keys()
# Loop through the keys since we can't pass the key set to the decoder
valid_token = False
for key in keys:
try:
# decode returns the claims that has the email when needed
jwt.decode(token, key=key, audience=POLICY_AUD, algorithms=['RS256'])
valid_token = True
break
except:
pass
if not valid_token:
return "invalid token", 403
return f()
return wrapper
@app.route('/')
@verify_token
def hello_world():
return 'Hello, World!'
if __name__ == '__main__':
app.run()Пример JavaScript (Node.js)
const express = require("express");
const jose = require("jose");
// The Application Audience (AUD) tag for your application
const AUD = process.env.POLICY_AUD;
// Your CF Access team domain
const TEAM_DOMAIN = process.env.TEAM_DOMAIN;
const CERTS_URL = `${TEAM_DOMAIN}/cdn-cgi/access/certs`;
const JWKS = jose.createRemoteJWKSet(new URL(CERTS_URL));
// verifyToken is a middleware to verify a CF authorization token
const verifyToken = async (req, res, next) => {
// Check for the AUD environment variable
if (!AUD) {
return res.status(403).send({
status: false,
message: "missing required audience",
});
}
const token = req.headers["cf-access-jwt-assertion"];
// Make sure that the incoming request has our token header
if (!token) {
return res.status(403).send({
status: false,
message: "missing required cf authorization token",
});
}
try {
const result = await jose.jwtVerify(token, JWKS, {
issuer: TEAM_DOMAIN,
audience: AUD,
});
req.user = result.payload;
next();
} catch (err) {
return res.status(403).send({
status: false,
message: "invalid token",
});
}
};
const app = express();
app.use(verifyToken);
app.get("/", (req, res) => {
res.send("Hello World!");
});
app.listen(3333);