Skip to main content

Authentication

Quick Reference

WhatOAuth 2.0 authentication via AWS Cognito
WhyGet a Bearer token to authorize every API request — with production-ready caching
Reading Time5 min
DifficultyBeginner
PrerequisitesIntroduction

Why token-based auth matters

Without proper auth managementWith A55 best practices
New token request on every API call — slow and rate-limitedCache and reuse the token for up to 1 hour
Credentials scattered in code, configs, and logsSingle secrets-manager entry, zero exposure
Expired tokens cause silent 401 failures in productionProactive refresh before expiry keeps uptime at 100%
Debugging auth issues wastes integration hoursOne flow, one endpoint, one token format — JWT everywhere
How it works

Exchange client_id + client_secret for a JWT access_token (valid for 1 hour). Send that token as Authorization: Bearer <token> on every API call. Refresh before it expires.


Authentication flow


Step 1 — Request an access token

SettingValue
Endpointhttps://smart-capital.auth.us-east-1.amazoncognito.com/oauth2/token
MethodPOST
Content-Typeapplication/x-www-form-urlencoded
FieldValue
grant_typeclient_credentials
client_idYour OAuth 2.0 client ID
client_secretYour OAuth 2.0 client secret
curl -s -X POST \
https://smart-capital.auth.us-east-1.amazoncognito.com/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"

Step 2 — Understand the token response

{
"access_token": "eyJraWQiOiJLTzZ...",
"expires_in": 3600,
"token_type": "Bearer"
}
FieldTypeDescription
access_tokenstringJWT used as the Bearer token in every API request
expires_inintegerToken lifetime in seconds (default 3600 = 1 hour)
token_typestringAlways Bearer
Decode the JWT

Paste your access_token into jwt.io during development to inspect claims, scopes, and expiry. Never do this with production tokens on untrusted sites.


Step 3 — Call the API

Send Authorization: Bearer <access_token> on every request.

curl -s -X GET \
https://core-manager.a55.tech/api/v1/wallets \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json"

Step 4 — Cache and refresh tokens

Tokens last 1 hour. Request a new one before it expires — never wait for a 401.

import time
import requests

class A55Client:
AUTH_URL = "https://smart-capital.auth.us-east-1.amazoncognito.com/oauth2/token"
API_BASE = "https://core-manager.a55.tech/api/v1"
REFRESH_MARGIN = 300 # refresh 5 min before expiry

def __init__(self, client_id: str, client_secret: str):
self._client_id = client_id
self._client_secret = client_secret
self._token = None
self._token_expiry = 0

def _refresh_token(self):
resp = requests.post(self.AUTH_URL, data={
"grant_type": "client_credentials",
"client_id": self._client_id,
"client_secret": self._client_secret,
}, headers={"Content-Type": "application/x-www-form-urlencoded"})
resp.raise_for_status()
data = resp.json()
self._token = data["access_token"]
self._token_expiry = time.time() + data["expires_in"] - self.REFRESH_MARGIN

@property
def token(self) -> str:
if not self._token or time.time() >= self._token_expiry:
self._refresh_token()
return self._token

def request(self, method: str, endpoint: str, **kwargs):
headers = {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json",
}
resp = requests.request(method, f"{self.API_BASE}/{endpoint}", headers=headers, **kwargs)
resp.raise_for_status()
return resp.json()

client = A55Client("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")
wallets = client.request("GET", "wallets")
print(wallets)

Token management checklist

PracticeWhy
Cache tokens for up to 55 minutesAvoids unnecessary Cognito calls and rate limits
Refresh 5 minutes before expires_inPrevents 401 errors during API calls
Store client_secret in a secrets managerEnv vars leak in logs; secrets managers do not
Never log tokens or secretsA leaked token grants full API access for 1 hour
Use HTTPS everywhereTokens in plaintext HTTP can be intercepted
Rotate secrets periodicallyLimits blast radius if a credential is compromised
Client secret is a password

Treat client_secret like a database password: backend-only storage, never in browsers, mobile apps, CI logs, or public repositories. If compromised, email tech.services@a55.tech immediately for rotation.


Error responses

HTTP statusMeaningWhat to do
401 UnauthorizedToken expired or invalidRefresh the token and retry
400 Bad RequestMalformed auth requestCheck grant_type, client_id, client_secret
429 Too Many RequestsRate limit hit on CognitoImplement exponential backoff; cache your tokens

Request credentials

Email tech.services@a55.tech with your company name, technical contact, and expected use case to receive client_id and client_secret for sandbox and production.