exports.onExecuteCustomTokenExchange = async (event, api) => { // 1. Validate subject_token const subject_token = await validateToken(event.transaction.subject_token, jwksUri); // 2. Apply your authorization policy on the user const isAuthorized = await authorizeAccess(subject_token.sub); if (!isAuthorized) { api.access.deny('Unauthorized_login', 'User cannot login due to reason: X'); } // 3. Set the user for the transaction api.authentication.setUserById(subject_token.sub); return;};
exports.onExecuteCustomTokenExchange = async (event, api) => { // Validate subject_token const subject_token = await validateToken(event.transaction.subject_token, jwksUri); // set the user for the transaction api.authentication.setUserById(subject_token.id); // set user group based on info contaiened in subject_token api.user.setAppMetadata('group', subject_token.group); return;};
exports.onExecuteCustomTokenExchange = async (event, api) => { // Validate subject_token const subject_token = await validateToken(event.transaction.subject_token, jwksUri); // set the user for the transaction api.authentication.setUserById(subject_token.id); // set user preferred_locale based on info contaiened in subject_token api.user.setUserMetadata('preferred_locale', subject_token.locale); return;};
exports.onExecuteCustomTokenExchange = async (event, api) => { // 1. Validate subject_token const subject_token = await validateToken(event.transaction.subject_token, jwksUri); // 2. Apply your authorization policy on the user const isAuthorized = await authorizeAccess(subject_token.sub); if (!isAuthorized) { api.access.deny('Unauthorized_login', 'User cannot login due to reason: X'); } // if user is authorized, go on as indicated here};
トランザクションを拒否して、要求元の外部IPアドレスについて試行の失敗数を加算します。カスタムトークン交換は要求をinvalid_requestのエラーコードを使用した400 Bad Requestエラー応答で拒否します。試行の失敗が最大数に達すると、該当するIPアドレスからのすべてのカスタムトークン交換要求について、Auth0はトラフィックをtoo_many_attemptsのエラーコードを使用した429 Too Many Requestsエラー応答でブロックします。詳細については、「攻撃防御」をお読みください。このメソッドは、署名や暗号化が不適切、または有効期限切れのサブジェクトトークンを含むカスタムトークン交換要求を受け取った場合には必ず使用してください。また、なりすましやリプレイ攻撃など、不正使用が疑われる状況でも必ず使用してください。そうすることで、Auth0は構成に応じて、不審なIPのスロットリングを適用できるようになります。不審なIPのスロットリングはデフォルトで最大10回まで、1時間あたりに6回の試行を許容します。詳細については、「攻撃防御」をお読みください。
パラメーター
説明
reason
応答でerror_descriptionプロパティに含めて返された文字列です。
Copy
Ask AI
exports.onExecuteCustomTokenExchange = async (event, api) => { try { // Validate subject_token const subject_token = await validateToken(event.transaction.subject_token, jwksUri); // set the user for the transaction api.authentication.setUserById(subject_token.id); } catch (error) { if (error.message === 'Invalid Token') { // If specifically the problem is the subject_token is invalid console.error('Invalid Token error'); api.access.rejectInvalidSubjectToken('Invalid subject_token'); } else { // if there is any other unexpected error, throw a server error throw error; } }};
HTTP/1.1 429 Too Many RequestsContent-Type: application/json{ "error": "too_many_attempts", "error_description": "We have detected suspicious login behavior and further attempts will be blocked. Please contact the administrator."}
/*** Handler to be executed while executing a custom token exchange request* @param {Event} event - Details about the incoming token exchange request.* @param {CustomTokenExchangeAPI} api - Methods and utilities to define token exchange process.*/exports.onExecuteCustomTokenExchange = async (event, api) => { // 1. VALIDATE the refresh_token received in the subject_token by using it to get // the UserProfile from the external IdP const { isValid, user } = await getUserProfile( event.transaction.subject_token, event.secrets.CLIENT_SECRET, ); if (!isValid) { // Mark the subject token as invalid and fail the transaction. api.access.rejectInvalidSubjectToken("Invalid subject_token"); } else { // 2. Apply your AUTHORIZATION POLICY as required to determine if the request is valid. // Use api.access.deny() to reject the transaction in those cases. // 3. When we have the profile, we SET THE USER in the target connection api.authentication.setUserByConnection( connectionName, { // only the user_id in the connection is needed, as we are not // creating nor updating the user user_id: user.sub, }, { creationBehavior: "none", updateBehavior: "none", }, ); }};/*** Exchange the refresh token and load the user profile from the legacy IdP* @param {string} refreshToken* @param {string} clientSecret* @returns {Promise<{ isValid: boolean, user?: object }>} If the refresh token was exchanged successfully, returns the user profile*/async function getUserProfile(refreshToken, clientSecret) { // Add your code here. REFER TO CODE SAMPLES FOR DETAILED EXAMPLES}
const jwksUri = "https://example.com/.well-known/jwks.json";/** * Handler to be executed while executing a custom token exchange request * @param {Event} event - Details about the incoming token exchange request. * @param {CustomTokenExchangeAPI} api - Methods and utilities to define token exchange process. */exports.onExecuteCustomTokenExchange = async (event, api) => { // 1. VALIDATE the id_token received in the subject_token const { isValid, payload } = await validateToken( event.transaction.subject_token, ); if (!isValid) { // Mark the subject token as invalid and fail the transaction. api.access.rejectInvalidSubjectToken("Invalid subject_token"); } else { // 2. Apply your AUTHORIZATION POLICY as required to determine if the request is valid. // Use api.access.deny() to reject the transaction in those cases. // 3. SET THE USER in the target connection. // We don't want to verify emails when users are created // This example assumes subject_token (id_token) contains standard OIDC claims. Other custom mappings // are also possible. api.authentication.setUserByConnection( 'Enterprise-OIDC', { user_id: formattedUserId, email: subject_token.email, email_verified: subject_token.email_verified, phone_number: subject_token.phone_number, phone_verified: subject_token.phone_number_verified, username: subject_token.preferred_username, name: subject_token.name, given_name: subject_token.given_name, family_name: subject_token.family_name, nickname: subject_token.nickname, verify_email: false }, { creationBehavior: 'create_if_not_exists', updateBehavior: 'none' } ); } /** * Validate the subject token * @param {string} subjectToken * @returns {Promise<{ isValid: boolean, payload?: object }>} Payload of the token */ async function validateToken(subjectToken) { // Add your code here. REFER TO CODE SAMPLES FOR DETAILED EXAMPLES }};
const jwksUri = "https://example.com/.well-known/jwks.json";/** * Handler to be executed while executing a custom token exchange request * @param {Event} event - Details about the incoming token exchange request. * @param {CustomTokenExchangeAPI} api - Methods and utilities to define token exchange process. */exports.onExecuteCustomTokenExchange = async (event, api) => { // 1. VALIDATE the access_token received in the subject_token const { isValid, payload } = await validateToken( event.transaction.subject_token, ); if (!isValid) { // Mark the subject token as invalid and fail the transaction. api.access.rejectInvalidSubjectToken("Invalid subject_token"); } else { // 2. Apply your AUTHORIZATION POLICY as required to determine if the request is valid. // Use api.access.deny() to reject the transaction in those cases. // 3. SET THE USER api.authentication.setUserById(payload.sub); } /** * Validate the subject token * @param {string} subjectToken * @returns {Promise<{ isValid: boolean, payload?: object }>} Payload of the token */ async function validateToken(subjectToken) { // Add your code here. REFER TO CODE SAMPLES FOR DETAILED EXAMPLES }};
const { jwtVerify } = require("jose");const jwksUri = "https://example.com/.well-known/jwks.json";const fetchTimeout = 5000; // 5 secondsconst validIssuer = "urn:my-issuer"; // Replace with your issuer/** * Handler to be executed while executing a custom token exchange request * @param {Event} event - Details about the incoming token exchange request. * @param {CustomTokenExchangeAPI} api - Methods and utilities to define token exchange process. */exports.onExecuteCustomTokenExchange = async (event, api) => { const { isValid, payload } = await validateToken( event.transaction.subject_token, ); // Apply your authorization policy as required to determine if the request is valid. // Use api.access.deny() to reject the transaction in those cases. if (!isValid) { // Mark the subject token as invalid and fail the transaction. api.access.rejectInvalidSubjectToken("Invalid subject_token"); } else { // Set the user in the current request as authenticated, using the user ID from the subject token. api.authentication.setUserById(payload.sub); } /** * Validate the subject token * @param {string} subjectToken * @returns {Promise<{ isValid: boolean, payload?: object }>} Payload of the token */ async function validateToken(subjectToken) { try { const { payload, protectedHeader } = await jwtVerify( subjectToken, async (header) => await getPublicKey(header.kid), { issuer: validIssuer, }, ); // Perform additional validation on the token payload as required return { isValid: true, payload }; } catch (/** @type {any} */ error) { if (error.message === "Error fetching JWKS") { throw new Error("Internal error - retry later"); } else { console.log("Token validation failed:", error.message); return { isValid: false }; } } } /** * Get the public key to use for key verification. Load from the actions cache if available, otherwise * fetch the key from the JWKS endpoint and store in the cache. * @param {string} kid - kid (Key ID) of the key to be used for verification * @returns {Promise<Object>} */ async function getPublicKey(kid) { const cachedKey = api.cache.get(kid); if (!cachedKey) { console.log(`Key ${kid} not found in cache`); const key = await fetchKeyFromJWKS(kid); api.cache.set(kid, JSON.stringify(key), { ttl: 600000 }); return key; } else { return JSON.parse(cachedKey.value); } } /** * Fetch public signing key from the provided JWKS endpoint, to use for token verification * @param {string} kid - kid (Key ID) of the key to be used for verification * @returns {Promise<object>} */ async function fetchKeyFromJWKS(kid) { const controller = new AbortController(); setTimeout(() => controller.abort(), fetchTimeout); /** @type {any} */ const response = await fetch(jwksUri); if (!response.ok) { console.log(`Error fetching JWKS. Response status: ${response.status}`); throw new Error("Error fetching JWKS"); } const jwks = await response.json(); const key = jwks.keys.find((key) => key.kid === kid); if (!key) { throw new Error("Key not found in JWKS"); } return key; }};
const { jwtVerify } = require("jose");const validIssuer = "urn:my-issuer"; // Replace with your issuer/** * Handler to be executed while executing a custom token exchange request * @param {Event} event - Details about the incoming token exchange request. * @param {CustomTokenExchangeAPI} api - Methods and utilities to define token exchange process. */exports.onExecuteCustomTokenExchange = async (event, api) => { // Initialize the shared symmetric key from Actions Secrets const encoder = new TextEncoder(); const symmetricKey = encoder.encode(event.secrets.SHARED_SECRET); const { isValid, payload } = await validateToken( event.transaction.subject_token, symmetricKey, ); // Apply your authorization policy as required to determine if the request is valid. // Use api.access.deny() to reject the transaction in those cases. if (!isValid) { // Mark the subject token as invalid and fail the transaction. api.access.rejectInvalidSubjectToken("Invalid subject_token"); } else { // Set the user in the current request as authenticated, using the user ID from the subject token. api.authentication.setUserById(payload.sub); }};/** * Validate the subject token * @param {string} subjectToken * @param {Uint8Array} symmetricKey * @returns {Promise<{ isValid: boolean, payload?: object }>} Payload of the token */async function validateToken(subjectToken, symmetricKey) { try { // Validate token is correctly signed with the shared symmetric key // It also checks it is not expired as long as it includes an 'exp' attribute. const { payload, protectedHeader } = await jwtVerify( subjectToken, symmetricKey, { issuer: validIssuer, }, ); return { isValid: true, payload }; } catch (/** @type {any} */ error) { console.log("Token validation failed:", error.message); return { isValid: false }; }}
const tokenEndpoint = "EXTERNAL_TOKEN_ ENDPOINT";const userInfoEndpoint = "EXTERNAL_USER_INFO_ENDPOINT";const clientId = "EXTERNAL_CLIENT_ID";const connectionName = "YOUR_CONNECTION_NAME";const fetchTimeout = 5000; // 5 seconds/** * Handler to be executed while executing a custom token exchange request * @param {Event} event - Details about the incoming token exchange request. * @param {CustomTokenExchangeAPI} api - Methods and utilities to define token exchange process. */exports.onExecuteCustomTokenExchange = async (event, api) => { const { isValid, user } = await getUserProfile( event.transaction.subject_token, event.secrets.CLIENT_SECRET, ); if (!isValid) { // Mark the subject token as invalid and fail the transaction. api.access.rejectInvalidSubjectToken("Invalid subject_token"); return; } // Apply your authorization policy as required to determine if the request is valid. // Use api.access.deny() to reject the transaction in those cases. // When we have the profile, we set the user in the target connection api.authentication.setUserByConnection( connectionName, { // only the user_id in the connection is needed, as we are not // creating nor updating the user user_id: user.sub, }, { creationBehavior: "none", updateBehavior: "none", }, );};/** * Exchange the refresh token and load the user profile from the legacy IdP * @param {string} refreshToken * @param {string} clientSecret * @returns {Promise<{ isValid: boolean, user?: object }>} If the refresh token was exchanged successfully, returns the user profile */async function getUserProfile(refreshToken, clientSecret) { const { isValid, accessToken } = await refreshAccessToken( refreshToken, clientSecret, ); if (!isValid) { return { isValid: false }; } const controller = new AbortController(); setTimeout(() => controller.abort(), fetchTimeout); /** @type {any} */ const response = await fetch(userInfoEndpoint, { method: "GET", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, }); if (!response.ok) { console.log(`Failed to fetch user info. Status: ${response.status}`); throw new Error("Error fetching user info"); } const userProfile = await response.json(); return { isValid: true, user: userProfile };}/** * Use the Refresh Token with the legacy IdP to validate it and get an access token * @param {string} refreshToken * @param {string} clientSecret * @returns {Promise<{ isValid: boolean, accessToken?: string }>} If the refresh token was exchanged successfully, returns the access token */async function refreshAccessToken(refreshToken, clientSecret) { const controller = new AbortController(); setTimeout(() => controller.abort(), fetchTimeout); /** @type {any} */ let response; try { response = await fetch(tokenEndpoint, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: clientId, client_secret: clientSecret, }).toString(), }); } catch (error) { console.error("Error refreshing token"); throw error; } if (!response.ok) { const errorBody = await response.json(); console.error("Error refreshing token:", errorBody.error); // If we receive an error indicating the refresh token is invalid (for example, an invalid_grant error), // then we should explicitly indicate an invalid token using api.access.rejectInvalidSubjectToken // to prevent against brute force attacks on the refresh token by activating Suspicious IP Throttling. // For other errors which indicate a generic error making the request to the IdP, we should throw // an error to indicate a transient failure. if (errorBody.error === "invalid_grant") { return { isValid: false }; } else { throw new Error("Error refreshing token"); } } // Parse the response, in the form { access_token: "...", expires_in: ..., } const data = await response.json(); console.log("Successfully exchanged refresh token"); return { isValid: true, accessToken: data.access_token };}