curl --request POST \
--url https://iam.basaltic.sh/v1/assume-role-with-web-identity \
--header 'Content-Type: application/json' \
--data '
{
"web_identity_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
"role": "crn:iam::production:role/reports",
"account": "crn:workspace:::account/9f8b1c2d-3e4f-4a5b-8c9d-0e1f2a3b4c5d",
"session_name": "reports-exporter",
"duration_seconds": 3600
}
'import requests
url = "https://iam.basaltic.sh/v1/assume-role-with-web-identity"
payload = {
"web_identity_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
"role": "crn:iam::production:role/reports",
"account": "crn:workspace:::account/9f8b1c2d-3e4f-4a5b-8c9d-0e1f2a3b4c5d",
"session_name": "reports-exporter",
"duration_seconds": 3600
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
web_identity_token: 'eyJhbGciOiJSUzI1NiIsImtpZCI6...',
role: 'crn:iam::production:role/reports',
account: 'crn:workspace:::account/9f8b1c2d-3e4f-4a5b-8c9d-0e1f2a3b4c5d',
session_name: 'reports-exporter',
duration_seconds: 3600
})
};
fetch('https://iam.basaltic.sh/v1/assume-role-with-web-identity', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://iam.basaltic.sh/v1/assume-role-with-web-identity",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'web_identity_token' => 'eyJhbGciOiJSUzI1NiIsImtpZCI6...',
'role' => 'crn:iam::production:role/reports',
'account' => 'crn:workspace:::account/9f8b1c2d-3e4f-4a5b-8c9d-0e1f2a3b4c5d',
'session_name' => 'reports-exporter',
'duration_seconds' => 3600
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://iam.basaltic.sh/v1/assume-role-with-web-identity"
payload := strings.NewReader("{\n \"web_identity_token\": \"eyJhbGciOiJSUzI1NiIsImtpZCI6...\",\n \"role\": \"crn:iam::production:role/reports\",\n \"account\": \"crn:workspace:::account/9f8b1c2d-3e4f-4a5b-8c9d-0e1f2a3b4c5d\",\n \"session_name\": \"reports-exporter\",\n \"duration_seconds\": 3600\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://iam.basaltic.sh/v1/assume-role-with-web-identity")
.header("Content-Type", "application/json")
.body("{\n \"web_identity_token\": \"eyJhbGciOiJSUzI1NiIsImtpZCI6...\",\n \"role\": \"crn:iam::production:role/reports\",\n \"account\": \"crn:workspace:::account/9f8b1c2d-3e4f-4a5b-8c9d-0e1f2a3b4c5d\",\n \"session_name\": \"reports-exporter\",\n \"duration_seconds\": 3600\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://iam.basaltic.sh/v1/assume-role-with-web-identity")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"web_identity_token\": \"eyJhbGciOiJSUzI1NiIsImtpZCI6...\",\n \"role\": \"crn:iam::production:role/reports\",\n \"account\": \"crn:workspace:::account/9f8b1c2d-3e4f-4a5b-8c9d-0e1f2a3b4c5d\",\n \"session_name\": \"reports-exporter\",\n \"duration_seconds\": 3600\n}"
response = http.request(request)
puts response.read_body{
"access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6Ii4uLiJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"access_key_id": "AKIA...",
"secret_access_key": "wJalrXUtnFEMI...",
"session_token": "FwoGZXIvYXdzE...",
"expiration": "2026-01-15T09:30:00Z",
"account_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"account_handle": "<string>",
"role_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}{
"error": {
"code": "INVALID_INPUT",
"message": "Invalid request parameters",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "UNAUTHORIZED",
"message": "invalid web identity token",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "IAM_CANNOT_ASSUME_ROLE",
"message": "You are not authorized to assume this role",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "NOT_FOUND",
"message": "Resource not found",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests, please try again later",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "INTERNAL_ERROR",
"message": "An internal error occurred",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "NOT_IMPLEMENTED",
"message": "web identity federation is not configured",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "The service is temporarily unavailable. Please try again later.",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}Asumir rol con identidad web
Intercambiar un token de identidad emitido por un proveedor de federación en el que confía esta plataforma por credenciales temporales. El resultado es la misma sesión de rol asumido POST /v1/assume-role mints, y se usa de la misma manera.
Esta solicitud lleva sin firma, y es la única llamada de venta de credenciales que no lo hace. Un llamador federado no tiene todavía credenciales de Basaltic, para eso es el intercambio, por lo que el token en el cuerpo es la credencial que se presenta. Una firma enviada de todas formas se ignora, y no se toma nada del contexto de la solicitud: role y account se leen del cuerpo como cualquier otro campo.
Eso no deja el punto final abierto. Dos puertas independientes tienen que pasar, y fallan de manera diferente.
El token debe verificarse. Esto sucede antes de que se lea cualquier rol, por lo que un token falsificado nunca alcanza una directiva de confianza. La firma debe encadenar a una clave que el proveedor publica, la audiencia debe ser la que esta plataforma acepta, y exp debe ser en el futuro. Un firmante incorrecto, un token acuñado para otro consumidor y un token caducado responden todos 401 con el mismo mensaje — la respuesta no dice qué comprobación falló.
El rol tiene que estar de acuerdo. Verificar el token establece quién está llamando; no otorga nada. El rol nombrado en role es asumible solo si su propia política de confianza admite esta identidad. Sus principals deben nombrar al proveedor de federación, escrito crn:iam:::oidc-provider/<provider> — el único caso en el que un principal de la política de confianza no es el CRN del propio llamador, porque una identidad federada no tiene CRN y lo que se confía es la fuente que lo garantiza. Cada entrada en conditions debe entonces mantenerse contra las declaraciones del token: basalt:webidentity:Subject lleva el sub del token y basalt:webidentity:Audience su aud, por lo que un rol puede vincular una identidad en lugar de aceptar todo lo que el proveedor emitirá. Una condición en una afirmación que el token no lleva falla cerrada.
Por lo tanto, un rol cuya directiva de confianza no nombre a ningún proveedor no puede asumirse de esta manera, por bueno que sea el token. Esa es la línea entre los dos fallos: 401 significa que el token no es confiable, 403 significa que lo es y el rol aún no lo tendrá.
Las credenciales vuelven con el alcance de account, llevando los permisos propios del rol. No hay ningún campo policy aquí — a diferencia de POST /v1/assume-role, una sesión federada no puede ser reducida en el momento del intercambio, por lo que las políticas adjuntas del rol son la concesión completa. Tamaño del papel en consecuencia.
Debido a que no requiere credenciales, las solicitudes están limitadas por la velocidad por IP del cliente.
En qué proveedores se confía es parte de la configuración de la propia plataforma. Todavía no hay una API para registrar un proveedor de identidad propio, por lo que esta operación está activa pero no tiene un proveedor externo cuyos tokens aceptaría; los roles que la usan hoy en día son administrados por la plataforma.
curl --request POST \
--url https://iam.basaltic.sh/v1/assume-role-with-web-identity \
--header 'Content-Type: application/json' \
--data '
{
"web_identity_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
"role": "crn:iam::production:role/reports",
"account": "crn:workspace:::account/9f8b1c2d-3e4f-4a5b-8c9d-0e1f2a3b4c5d",
"session_name": "reports-exporter",
"duration_seconds": 3600
}
'import requests
url = "https://iam.basaltic.sh/v1/assume-role-with-web-identity"
payload = {
"web_identity_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
"role": "crn:iam::production:role/reports",
"account": "crn:workspace:::account/9f8b1c2d-3e4f-4a5b-8c9d-0e1f2a3b4c5d",
"session_name": "reports-exporter",
"duration_seconds": 3600
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
web_identity_token: 'eyJhbGciOiJSUzI1NiIsImtpZCI6...',
role: 'crn:iam::production:role/reports',
account: 'crn:workspace:::account/9f8b1c2d-3e4f-4a5b-8c9d-0e1f2a3b4c5d',
session_name: 'reports-exporter',
duration_seconds: 3600
})
};
fetch('https://iam.basaltic.sh/v1/assume-role-with-web-identity', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://iam.basaltic.sh/v1/assume-role-with-web-identity",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'web_identity_token' => 'eyJhbGciOiJSUzI1NiIsImtpZCI6...',
'role' => 'crn:iam::production:role/reports',
'account' => 'crn:workspace:::account/9f8b1c2d-3e4f-4a5b-8c9d-0e1f2a3b4c5d',
'session_name' => 'reports-exporter',
'duration_seconds' => 3600
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://iam.basaltic.sh/v1/assume-role-with-web-identity"
payload := strings.NewReader("{\n \"web_identity_token\": \"eyJhbGciOiJSUzI1NiIsImtpZCI6...\",\n \"role\": \"crn:iam::production:role/reports\",\n \"account\": \"crn:workspace:::account/9f8b1c2d-3e4f-4a5b-8c9d-0e1f2a3b4c5d\",\n \"session_name\": \"reports-exporter\",\n \"duration_seconds\": 3600\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://iam.basaltic.sh/v1/assume-role-with-web-identity")
.header("Content-Type", "application/json")
.body("{\n \"web_identity_token\": \"eyJhbGciOiJSUzI1NiIsImtpZCI6...\",\n \"role\": \"crn:iam::production:role/reports\",\n \"account\": \"crn:workspace:::account/9f8b1c2d-3e4f-4a5b-8c9d-0e1f2a3b4c5d\",\n \"session_name\": \"reports-exporter\",\n \"duration_seconds\": 3600\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://iam.basaltic.sh/v1/assume-role-with-web-identity")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"web_identity_token\": \"eyJhbGciOiJSUzI1NiIsImtpZCI6...\",\n \"role\": \"crn:iam::production:role/reports\",\n \"account\": \"crn:workspace:::account/9f8b1c2d-3e4f-4a5b-8c9d-0e1f2a3b4c5d\",\n \"session_name\": \"reports-exporter\",\n \"duration_seconds\": 3600\n}"
response = http.request(request)
puts response.read_body{
"access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6Ii4uLiJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"access_key_id": "AKIA...",
"secret_access_key": "wJalrXUtnFEMI...",
"session_token": "FwoGZXIvYXdzE...",
"expiration": "2026-01-15T09:30:00Z",
"account_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"account_handle": "<string>",
"role_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}{
"error": {
"code": "INVALID_INPUT",
"message": "Invalid request parameters",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "UNAUTHORIZED",
"message": "invalid web identity token",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "IAM_CANNOT_ASSUME_ROLE",
"message": "You are not authorized to assume this role",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "NOT_FOUND",
"message": "Resource not found",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests, please try again later",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "INTERNAL_ERROR",
"message": "An internal error occurred",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "NOT_IMPLEMENTED",
"message": "web identity federation is not configured",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "The service is temporarily unavailable. Please try again later.",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}Cuerpo
El intercambio que envía un llamador federado. No lleva firma — el token es la credencial — por lo que cada campo se lee desde el cuerpo y nada se infiere del contexto de la solicitud.
El token de identidad a intercambiar, como un JWT firmado. Se verifica antes de que se lea cualquier rol: la firma debe encadenar a una clave que el proveedor de confianza publica, la audiencia debe ser la que esta plataforma fue configurada para aceptar, y exp debe estar en el futuro.
"eyJhbGciOiJSUzI1NiIsImtpZCI6..."
UUID del rol de la cuenta, nombre inmutable en la cuenta seleccionada, o crn:iam:::función/. AssumeRole puede usar un CRN calificado para dirigirse a otra cuenta de la misma organización, y la sesión resultante se vincula a la cuenta de rol de destino.
"crn:iam::production:role/reports"
UUID de la cuenta, identificador inmutable o CRN de una cuenta de espacio de trabajo (crn:workspace:::account/La cuenta establece la organización propietaria para la federación y debe coincidir con la cuenta de rol de destino.
"crn:workspace:::account/9f8b1c2d-3e4f-4a5b-8c9d-0e1f2a3b4c5d"
Una etiqueta registrada en la sesión y en el seguimiento de auditoría. Por defecto, se establece en la afirmación sub del token, por lo que una sesión sin nombre todavía registra de qué identidad proviene.
"reports-exporter"
Duración de validez de la credencial (15 min a 12 horas). Un valor por encima de la propia max_session_duration del rol es rechazado en lugar de ser sujetado.
900 <= x <= 432003600
Respuesta
Función asumida con éxito
Las credenciales de una sesión de rol, en ambas formas que se pueden presentar.
access_token es un token portador para esta API — envíelo como Authorization: Bearer <token>. Los otros cuatro campos son credenciales de AWS SigV4 para el punto final de almacenamiento de objetos compatible con S3, que no dice nada más.
Ambos provienen de la misma sesión y comparten su vencimiento, por lo que revocar la sesión detiene a ambos a la vez. Utilice cualquiera que sea el punto final que está llamando; no hay necesidad de elegir uno en el momento de la solicitud.
Token portador para la API de Basaltic. Presencia en cada sesión de rol.
"eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6Ii4uLiJ9..."
Siempre Bearer cuando access_token está presente.
"Bearer"
Segundos hasta que access_token expira.
3600
Id de clave de acceso SigV4, para el punto final S3.
"AKIA..."
Secreto SigV4, para el punto final S3.
"wJalrXUtnFEMI..."
Token de sesión SigV4, para el punto final S3. Enviar como X-Amz-Security-Token e incluirlo en SignedHeaders.
"FwoGZXIvYXdzE..."
Cuando la sesión —y por lo tanto ambos formularios de credenciales— caduca.
"2026-01-15T09:30:00Z"
UUID de la cuenta propietaria del rol de destino.
Identificador de cuenta de propietario del rol de destino.
UUID inmutable del rol asumido.

