Obtendo Access Tokens
O DVS usa o OAuth2 client credentials grant para autenticação service-to-service. O OSIGU Auth Server usa HTTP Basic auth para identificação do client e query string para o grant type — esta página apresenta o formato exato da request e implementações prontas para copiar em 5 linguagens.
Endpoints
| Ambiente | Token URL |
|---|---|
| Sandbox | https://sandbox.osigu.com/v1/oauth/token |
| Produção | https://api.osigu.com/v1/oauth/token |
As credenciais são escopadas por ambiente — o client_id de sandbox não funciona contra a token URL de produção.
Formato da request
grant_typestringrequiredEnviado como parâmetro de query string (não no body). Sempre client_credentials.
AuthorizationheaderrequiredHTTP Basic auth: Basic <base64(client_id:client_secret)>. Use as credenciais emitidas pela OSIGU para o ambiente alvo.
Method: POST
Body: vazio
Content-Type: não necessário (sem body)
Response
{
"access_token": "7dd4f350-676e-4257-9d7b-f3c5ac4dfi14",
"token_type": "bearer",
"expires_in": 86399,
"scope": "read write",
"extensions": {
"provider_slug": "br-gamma"
}
}
| Campo | Descrição |
|---|---|
access_token | Token opaco (não é um JWT). Envie como Authorization: Bearer <token> em chamadas subsequentes à DVS API. |
token_type | Sempre bearer. |
expires_in | Segundos até a expiração. Padrão é 86399 (~24 horas). |
scope | Scope OAuth2 grosso concedido ao token (ex.: read write). As authorities mais finas por endpoint do DVS são aplicadas server-side a partir da configuração do client_id. |
extensions | Mapa de metadados de formato livre. Inclui o provider_slug (utilize-o para registrar a qual tenant o token pertence). Pode incluir outros campos escopados ao tenant que a OSIGU adicione no futuro. |
Exemplos de código
- curl
- Python
- Node.js
- PHP
- Java
curl --location --request POST \
"https://sandbox.osigu.com/v1/oauth/token?grant_type=client_credentials" \
--header "Authorization: Basic $(echo -n "$DVS_CLIENT_ID:$DVS_CLIENT_SECRET" | base64)"
import base64
import os
import time
import httpx
from threading import Lock
TOKEN_URL = "https://sandbox.osigu.com/v1/oauth/token" # troque para api.osigu.com em produção
CLIENT_ID = os.environ["DVS_CLIENT_ID"]
CLIENT_SECRET = os.environ["DVS_CLIENT_SECRET"]
_cache = {"access_token": None, "expires_at": 0.0, "provider_slug": None}
_lock = Lock()
def get_access_token() -> str:
"""Retorna um access token válido, renovando 60s antes da expiração."""
with _lock:
if _cache["access_token"] and time.time() < _cache["expires_at"] - 60:
return _cache["access_token"]
basic = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()
response = httpx.post(
TOKEN_URL,
params={"grant_type": "client_credentials"},
headers={"Authorization": f"Basic {basic}"},
timeout=10,
)
response.raise_for_status()
payload = response.json()
_cache["access_token"] = payload["access_token"]
_cache["expires_at"] = time.time() + payload["expires_in"]
_cache["provider_slug"] = payload.get("extensions", {}).get("provider_slug")
return _cache["access_token"]
const TOKEN_URL = "https://sandbox.osigu.com/v1/oauth/token"; // troque para api.osigu.com em produção
let cached = { token: null, expiresAt: 0, providerSlug: null };
async function getAccessToken() {
if (cached.token && Date.now() < cached.expiresAt - 60_000) {
return cached.token;
}
const basic = Buffer.from(
`${process.env.DVS_CLIENT_ID}:${process.env.DVS_CLIENT_SECRET}`,
).toString("base64");
const response = await fetch(
`${TOKEN_URL}?grant_type=client_credentials`,
{
method: "POST",
headers: { Authorization: `Basic ${basic}` },
},
);
if (!response.ok) {
throw new Error(`Auth failed: ${response.status}`);
}
const payload = await response.json();
cached = {
token: payload.access_token,
expiresAt: Date.now() + payload.expires_in * 1000,
providerSlug: payload.extensions?.provider_slug ?? null,
};
return cached.token;
}
<?php
const TOKEN_URL = "https://sandbox.osigu.com/v1/oauth/token"; // troque para api.osigu.com em produção
function getAccessToken(): string {
static $cache = ["token" => null, "expires_at" => 0, "provider_slug" => null];
if ($cache["token"] && time() < $cache["expires_at"] - 60) {
return $cache["token"];
}
$basic = base64_encode(getenv("DVS_CLIENT_ID") . ":" . getenv("DVS_CLIENT_SECRET"));
$ch = curl_init(TOKEN_URL . "?grant_type=client_credentials");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Basic $basic"]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new RuntimeException("Auth failed: HTTP $status");
}
$payload = json_decode($body, true);
$cache["token"] = $payload["access_token"];
$cache["expires_at"] = time() + $payload["expires_in"];
$cache["provider_slug"] = $payload["extensions"]["provider_slug"] ?? null;
return $cache["token"];
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Base64;
import org.json.JSONObject;
public class DvsAuth {
private static final String TOKEN_URL = "https://sandbox.osigu.com/v1/oauth/token"; // troque em produção
private static final HttpClient HTTP = HttpClient.newHttpClient();
private static volatile String cachedToken;
private static volatile Instant cachedExpiresAt = Instant.EPOCH;
private static volatile String cachedProviderSlug;
public static synchronized String getAccessToken() throws Exception {
if (cachedToken != null && Instant.now().isBefore(cachedExpiresAt.minusSeconds(60))) {
return cachedToken;
}
String creds = System.getenv("DVS_CLIENT_ID") + ":" + System.getenv("DVS_CLIENT_SECRET");
String basic = Base64.getEncoder().encodeToString(creds.getBytes(StandardCharsets.UTF_8));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(TOKEN_URL + "?grant_type=client_credentials"))
.header("Authorization", "Basic " + basic)
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = HTTP.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("Auth failed: " + response.statusCode());
}
JSONObject json = new JSONObject(response.body());
cachedToken = json.getString("access_token");
cachedExpiresAt = Instant.now().plusSeconds(json.getInt("expires_in"));
if (json.has("extensions")) {
JSONObject ext = json.getJSONObject("extensions");
cachedProviderSlug = ext.optString("provider_slug", null);
}
return cachedToken;
}
}
Boas práticas
Faça cache dos tokens
Tokens são válidos por ~24 horas por padrão. Faça cache em memória (ou em cache compartilhado como Redis para serviços multi-instância) e renove ~60 segundos antes da expiração. Chamar o endpoint do OAuth a cada request da API resultará em rate-limit e latência desnecessária.
Armazene secrets em um vault, não em arquivos de env
Use AWS Secrets Manager, HashiCorp Vault, Doppler ou equivalente. Rotacione o client_secret anualmente (ou após qualquer suspeita de exposição) entrando em contato com a OSIGU.
Trate 401 com refresh
Se uma request retornar 401, o token pode ter expirado ou sido revogado. Descarte o token em cache, solicite um novo e tente a request original uma vez. Não faça retry infinitamente.
Não faça log dos tokens
Remova os headers Authorization dos logs da aplicação. Um token vazado é válido por até 24 horas.
Use extensions.provider_slug para observabilidade
Registre em log o provider_slug da response do token junto com os request IDs. Isso torna a correlação de incidentes com o suporte da OSIGU muito mais rápida.
Erros
| Status | Body | Causa |
|---|---|---|
400 | error: "invalid_request" | Falta o grant_type na query string. |
401 | error: "invalid_client" | client_id ou client_secret incorretos, ou ambiente errado (credenciais de sandbox contra URL de produção). |
405 | Method Not Allowed | Foi usado GET em vez de POST. |
429 | rate limited | Muitas requests de token. Faça cache e reduza o ritmo. |
5xx | server error | Indisponibilidade do Auth Server. Faça retry com exponential backoff (máximo 3). |