Skip to content

Instantly share code, notes, and snippets.

@aaguiarz
Created March 11, 2026 19:47
Show Gist options
  • Select an option

  • Save aaguiarz/8b3e34f1e5fbba878ec337272335a5e5 to your computer and use it in GitHub Desktop.

Select an option

Save aaguiarz/8b3e34f1e5fbba878ec337272335a5e5 to your computer and use it in GitHub Desktop.
-- Kong Pre-function Plugin - Auth0 Token Vault Access Token Exchange
-- Reference: https://auth0.com/docs/secure/tokens/token-vault/access-token-exchange-with-token-vault
--
-- This plugin performs Auth0 Token Vault access token exchange:
-- 1. Client sends Auth0 access token
-- 2. Kong exchanges it for the external provider's token via Auth0
-- 3. Kong forwards the request with the external token
--
-- Token validation is handled by Auth0 during the exchange (no local JWT validation)
--
-- Configure per Kong Service for each external API (Google, GitHub, Slack, etc.)
-- Connection is automatically determined from the Kong service host
--
-- Required Environment Variables:
-- AUTH0_DOMAIN - Your Auth0 tenant domain (e.g., "your-tenant.auth0.com")
-- AUTH0_CLIENT_ID - Custom API Client ID (linked to your backend API)
-- AUTH0_CLIENT_SECRET - Custom API Client secret
local http = require "resty.http"
local cjson = require "cjson.safe"
local resty_sha256 = require "resty.sha256"
local str = require "resty.string"
-- Configuration
local AUTH0_DOMAIN = os.getenv("AUTH0_DOMAIN")
local AUTH0_CLIENT_ID = os.getenv("AUTH0_CLIENT_ID")
local AUTH0_CLIENT_SECRET = os.getenv("AUTH0_CLIENT_SECRET")
-- Host to Auth0 Connection mapping
-- Map external service hosts to their Auth0 connection names
local HOST_TO_CONNECTION = {
-- Google services
["www.googleapis.com"] = "google-oauth2",
["gmail.googleapis.com"] = "google-oauth2",
["calendar.google.com"] = "google-oauth2",
["drive.google.com"] = "google-oauth2",
-- GitHub
["api.github.com"] = "github",
}
-- Validate required configuration
if not AUTH0_DOMAIN or not AUTH0_CLIENT_ID or not AUTH0_CLIENT_SECRET then
kong.log.err("Missing Auth0 configuration: AUTH0_DOMAIN, AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET required")
return kong.response.exit(500, {
error = "configuration_error",
error_description = "Auth0 credentials not configured"
})
end
-- Generate SHA256 hash of input for cache keys
local function sha256_hash(input)
local sha = resty_sha256:new()
sha:update(input)
local digest = sha:final()
return str.to_hex(digest)
end
-- Perform Token Vault access token exchange
local function exchange_token(subject_token, connection)
local httpc = http.new()
httpc:set_timeout(5000)
-- Token exchange request per Auth0 Token Vault spec
local request_body = {
client_id = AUTH0_CLIENT_ID,
client_secret = AUTH0_CLIENT_SECRET,
subject_token = subject_token,
grant_type = "urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token",
subject_token_type = "urn:ietf:params:oauth:token-type:access_token",
requested_token_type = "http://auth0.com/oauth/token-type/federated-connection-access-token",
connection = connection
}
local res, err = httpc:request_uri("https://" .. AUTH0_DOMAIN .. "/oauth/token", {
method = "POST",
body = cjson.encode(request_body),
headers = { ["Content-Type"] = "application/json" },
ssl_verify = true
})
httpc:close()
if not res then
kong.log.err("Token exchange request failed: ", err)
return nil, "network_error", "Failed to connect to Auth0"
end
local response_data = cjson.decode(res.body)
if res.status ~= 200 then
kong.log.err("Token exchange failed: ", res.status, " - ", res.body)
return nil,
response_data and response_data.error or "token_exchange_failed",
response_data and response_data.error_description or "Token exchange failed"
end
if not response_data or not response_data.access_token then
return nil, "invalid_response", "Missing access_token in response"
end
return response_data.access_token, nil, nil, response_data.expires_in
end
-- Determine Auth0 connection from Kong service host
local service = kong.router.get_service()
if not service or not service.host then
kong.log.err("Could not determine Kong service host")
return kong.response.exit(500, {
error = "configuration_error",
error_description = "Could not determine target service"
})
end
local connection = HOST_TO_CONNECTION[service.host]
if not connection then
kong.log.warn("No connection mapping found for host: ", service.host)
return kong.response.exit(400, {
error = "connection_not_found",
error_description = "Could not determine Auth0 connection for host: " .. service.host
})
end
kong.log.debug("Auth0 Token Vault exchange - connection: ", connection, " for host: ", service.host)
-- Extract Auth0 access token
local auth_header = kong.request.get_header("authorization")
if not auth_header then
return kong.response.exit(401, {
error = "unauthorized",
error_description = "Authorization header required"
})
end
local subject_token = auth_header:match("^Bearer%s+(.+)$")
if not subject_token then
return kong.response.exit(401, {
error = "invalid_token",
error_description = "Invalid Authorization header format"
})
end
-- Generate cache key using SHA256 hash of token + connection
-- This ensures only the exchanged token is cached, not the Auth0 token
local cache_key = "token_vault:" .. sha256_hash(subject_token .. ":" .. connection)
-- Try to get cached token from Kong's shared cache
local external_token, err = kong.cache:get(cache_key, nil, function()
-- Cache miss - perform token exchange
kong.log.debug("Cache miss - exchanging token for connection: ", connection)
local token, error_code, error_message, expires_in = exchange_token(subject_token, connection)
if not token then
kong.log.err("Token exchange failed for ", connection, ": ", error_code, " - ", error_message)
-- Return nil to not cache errors
return nil
end
-- Subtract 60 seconds as a safety buffer before expiration
local ttl = expires_in and (expires_in - 60) or 300
kong.log.info("Token exchanged for connection: ", connection, " with TTL: ", ttl, "s")
-- Return token and TTL for Kong cache
return { token = token }, nil, ttl
end)
if err then
kong.log.err("Cache error: ", err)
return kong.response.exit(500, {
error = "cache_error",
error_description = "Failed to retrieve cached token"
})
end
if not external_token or not external_token.token then
return kong.response.exit(401, {
error = "token_exchange_failed",
error_description = "Failed to exchange token",
connection = connection
})
end
-- Replace Auth0 token with external provider token
kong.service.request.set_header("Authorization", "Bearer " .. external_token.token)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment