curl --request POST \
--url https://iam.basaltic.sh/v1/oauth/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=client_credentials \
--data client_id=BYCLD1a2b3c4d5e6f7 \
--data 'client_secret=<string>' \
--data duration_seconds=3600 \
--data 'code=<string>' \
--data 'code_verifier=<string>' \
--data redirect_uri=urn:ietf:wg:oauth:2.0:oob \
--data 'refresh_token=<string>'import requests
url = "https://iam.basaltic.sh/v1/oauth/token"
payload = {
"grant_type": "client_credentials",
"client_id": "BYCLD1a2b3c4d5e6f7",
"client_secret": "<string>",
"duration_seconds": "3600",
"code": "<string>",
"code_verifier": "<string>",
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
"refresh_token": "<string>"
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = requests.post(url, data=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: 'BYCLD1a2b3c4d5e6f7',
client_secret: '<string>',
duration_seconds: '3600',
code: '<string>',
code_verifier: '<string>',
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
refresh_token: '<string>'
})
};
fetch('https://iam.basaltic.sh/v1/oauth/token', 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/oauth/token",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "grant_type=client_credentials&client_id=BYCLD1a2b3c4d5e6f7&client_secret=%3Cstring%3E&duration_seconds=3600&code=%3Cstring%3E&code_verifier=%3Cstring%3E&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&refresh_token=%3Cstring%3E",
CURLOPT_HTTPHEADER => [
"Content-Type: application/x-www-form-urlencoded"
],
]);
$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/oauth/token"
payload := strings.NewReader("grant_type=client_credentials&client_id=BYCLD1a2b3c4d5e6f7&client_secret=%3Cstring%3E&duration_seconds=3600&code=%3Cstring%3E&code_verifier=%3Cstring%3E&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&refresh_token=%3Cstring%3E")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
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/oauth/token")
.header("Content-Type", "application/x-www-form-urlencoded")
.body("grant_type=client_credentials&client_id=BYCLD1a2b3c4d5e6f7&client_secret=%3Cstring%3E&duration_seconds=3600&code=%3Cstring%3E&code_verifier=%3Cstring%3E&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&refresh_token=%3Cstring%3E")
.asString();require 'uri'
require 'net/http'
url = URI("https://iam.basaltic.sh/v1/oauth/token")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/x-www-form-urlencoded'
request.body = "grant_type=client_credentials&client_id=BYCLD1a2b3c4d5e6f7&client_secret=%3Cstring%3E&duration_seconds=3600&code=%3Cstring%3E&code_verifier=%3Cstring%3E&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&refresh_token=%3Cstring%3E"
response = http.request(request)
puts response.read_body{
"access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6Ii4uLiJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "<string>"
}{
"error": "unsupported_grant_type",
"error_description": "unsupported grant type"
}{
"error": "invalid_client",
"error_description": "client authentication failed"
}{
"error": "invalid_grant",
"error_description": "Organization is suspended"
}{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests, please try again later",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": "temporarily_unavailable",
"error_description": "try again shortly"
}Troque uma chave de acesso por um token de portador
Troque o par de chaves de acesso de uma conta de serviço por um token de portador de curta duração e, em seguida, envie esse token como Authorization: Bearer <token> em cada outra chamada.
Esta é a forma normal de autenticação. O par de chaves de acesso permanece a única credencial de longa duração que uma conta de serviço tem; o que muda é que você apresenta um token derivado dele em vez de assinar cada solicitação.
curl -s -u "$KEY_ID:$SECRET" -d grant_type=client_credentials \
https://iam.basaltic.sh/v1/oauth/token
O mesmo par de chaves é também a credencial AWS SigV4 para o endpoint de objeto compatível com o S3, o que não diz mais nada. Use o token para essa API e o par de chaves para o S3; não há necessidade de escolher.
Os erros aqui usam a forma OAuth 2.0, não o envelope usual desta API — {"error": "...", "error_description": "..."} — porque cada biblioteca cliente OAuth analisa isso e nada mais. Duas respostas importam e seus remédios são opostos. invalid_client significa que a chave foi rejeitada: verifique ou gire-a. invalid_grant significa que a chave está bem e a organização está suspensa ou ainda está sendo integrada, onde a rotação de uma chave de trabalho desperdiçaria seu tempo.
Uma chave de acesso desconhecida e um segredo errado respondem a invalid_client com a mesma mensagem, então o endpoint não pode ser usado para descobrir quais chaves existem.
curl --request POST \
--url https://iam.basaltic.sh/v1/oauth/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=client_credentials \
--data client_id=BYCLD1a2b3c4d5e6f7 \
--data 'client_secret=<string>' \
--data duration_seconds=3600 \
--data 'code=<string>' \
--data 'code_verifier=<string>' \
--data redirect_uri=urn:ietf:wg:oauth:2.0:oob \
--data 'refresh_token=<string>'import requests
url = "https://iam.basaltic.sh/v1/oauth/token"
payload = {
"grant_type": "client_credentials",
"client_id": "BYCLD1a2b3c4d5e6f7",
"client_secret": "<string>",
"duration_seconds": "3600",
"code": "<string>",
"code_verifier": "<string>",
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
"refresh_token": "<string>"
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = requests.post(url, data=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: 'BYCLD1a2b3c4d5e6f7',
client_secret: '<string>',
duration_seconds: '3600',
code: '<string>',
code_verifier: '<string>',
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
refresh_token: '<string>'
})
};
fetch('https://iam.basaltic.sh/v1/oauth/token', 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/oauth/token",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "grant_type=client_credentials&client_id=BYCLD1a2b3c4d5e6f7&client_secret=%3Cstring%3E&duration_seconds=3600&code=%3Cstring%3E&code_verifier=%3Cstring%3E&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&refresh_token=%3Cstring%3E",
CURLOPT_HTTPHEADER => [
"Content-Type: application/x-www-form-urlencoded"
],
]);
$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/oauth/token"
payload := strings.NewReader("grant_type=client_credentials&client_id=BYCLD1a2b3c4d5e6f7&client_secret=%3Cstring%3E&duration_seconds=3600&code=%3Cstring%3E&code_verifier=%3Cstring%3E&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&refresh_token=%3Cstring%3E")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
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/oauth/token")
.header("Content-Type", "application/x-www-form-urlencoded")
.body("grant_type=client_credentials&client_id=BYCLD1a2b3c4d5e6f7&client_secret=%3Cstring%3E&duration_seconds=3600&code=%3Cstring%3E&code_verifier=%3Cstring%3E&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&refresh_token=%3Cstring%3E")
.asString();require 'uri'
require 'net/http'
url = URI("https://iam.basaltic.sh/v1/oauth/token")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/x-www-form-urlencoded'
request.body = "grant_type=client_credentials&client_id=BYCLD1a2b3c4d5e6f7&client_secret=%3Cstring%3E&duration_seconds=3600&code=%3Cstring%3E&code_verifier=%3Cstring%3E&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&refresh_token=%3Cstring%3E"
response = http.request(request)
puts response.read_body{
"access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6Ii4uLiJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "<string>"
}{
"error": "unsupported_grant_type",
"error_description": "unsupported grant type"
}{
"error": "invalid_client",
"error_description": "client authentication failed"
}{
"error": "invalid_grant",
"error_description": "Organization is suspended"
}{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests, please try again later",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": "temporarily_unavailable",
"error_description": "try again shortly"
}Corpo
Uma solicitação de token OAuth 2.0. O codificado em formulário é o que a RFC 6749 especifica e o que as bibliotecas cliente enviam; JSON também é aceito.
As credenciais do cliente podem ser enviadas como HTTP Basic (Authorization: Basic base64(key_id:secret), que é o que a maioria das bibliotecas faz por padrão) ou como campos client_id e client_secret. Basic ganha se ambos estiverem presentes.
client_credentials é o que se usa para uma conta de serviço: ele troca um par de chaves de acesso por um token, e não precisa de mais nada.
authorization_code e refresh_token pertencem ao login interativo que uma pessoa executa (basaltic login), onde o token nomeia um USER ao invés de uma conta de serviço. Eles são conduzidos pela CLI, não escritos à mão. Verifique o documento de metadados do servidor de autorização antes de ramificar neles — eles são anunciados somente onde um endpoint de autorização está configurado.
client_credentials, authorization_code, refresh_token "client_credentials"
O id da chave de acesso. Omita ao usar HTTP Basic.
"BYCLD1a2b3c4d5e6f7"
A chave de acesso secreta. Omita ao usar HTTP Basic.
Tempo de vida do token solicitado. Uma extensão Basaltic, não um parâmetro OAuth — omita-o e você obtém o padrão. Valores fora do intervalo são fixados nele em vez de serem recusados, então pedir um dia produz o token mais longo permitido.
900 <= x <= 432003600
O código de autorização do redirecionamento de consentimento. Uso único, e válido por cinco minutos. authorization_code concede apenas.
O verificador PKCE cujo SHA-256 foi enviado como code_challenge quando o fluxo começou (RFC 7636). Requerido com authorization_code: é o que prova que este é o cliente que iniciou o fluxo, uma vez que uma CLI não mantém nenhum segredo de cliente.
O mesmo redirect_uri para o qual o código foi emitido — para a CLI, urn:ietf:wg:oauth:2.0:oob. Re-checado aqui, para que um código não possa ser resgatado sob um diferente (RFC 6749 4.1.3).
"urn:ietf:wg:oauth:2.0:oob"
refresh_token concede apenas. Renova uma sessão de usuário sem outra viagem através do navegador. Rotação a cada uso — guarde o novo.
Resposta
Um token de portador
Resposta de token RFC 6749.
Enviar como Authorization: Bearer <token>. Opacos para os clientes: não analisem e não digitem nada na string de token.
"eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6Ii4uLiJ9..."
Bearer "Bearer"
Segundos até que o token expire.
3600
Retornado apenas pelas concessões do usuário (authorization_code e refresh_token). Apresente-o ao refresh_token para renovar sem outra viagem de ida e volta do navegador; ele é ROTATADO em cada uso, então substitua a cópia armazenada toda vez.
Uma conta de serviço não recebe nenhuma. Ele já possui uma chave de acesso de longa duração e pode simplesmente executar client_credentials novamente, então um token de atualização seria uma segunda credencial para armazenar sem ganho.

