Pular para o conteúdo principal

Authentication

Overview

The Cidadão.AI API implements a multi-layered authentication system based on industry standards, providing secure access control through JWT tokens, OAuth 2.0 integration, and role-based permissions.

Authentication Methods

1. JWT Authentication (Primary)

The API uses JSON Web Tokens (JWT) as the primary authentication mechanism, following RFC 7519 specifications.

Token Structure

{
"header": {
"alg": "RS256",
"typ": "JWT",
"kid": "2024-01-key"
},
"payload": {
"sub": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"role": "user",
"permissions": ["read:investigations", "write:reports"],
"iat": 1706616000,
"exp": 1706616900,
"jti": "unique-token-id"
},
"signature": "..."
}

Token Lifecycle

2. OAuth 2.0 Integration

Support for third-party authentication providers:

  • Google (OpenID Connect)
  • GitHub (OAuth 2.0)
  • Microsoft (Azure AD)
  • Custom SAML/OIDC providers

OAuth Flow

3. API Key Authentication (Legacy)

For backwards compatibility and service-to-service communication:

X-API-Key: sk_live_50e8400-e29b-41d4-a716-446655440000

Authentication Endpoints

Login

POST /api/v1/auth/login
Content-Type: application/json

{
"email": "user@example.com",
"password": "secure-password"
}

Response:

{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 900,
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"role": "user",
"permissions": ["read:investigations", "write:reports"]
}
}

Token Refresh

POST /api/v1/auth/refresh
Content-Type: application/json

{
"refresh_token": "eyJhbGciOiJSUzI1NiIs..."
}

User Registration

POST /api/v1/auth/register
Authorization: Bearer {admin_token}
Content-Type: application/json

{
"email": "newuser@example.com",
"password": "secure-password",
"role": "analyst"
}

Change Password

POST /api/v1/auth/change-password
Authorization: Bearer {token}
Content-Type: application/json

{
"current_password": "old-password",
"new_password": "new-secure-password"
}

Authorization

Role-Based Access Control (RBAC)

RoleDescriptionPermissions
adminSystem administratorFull access to all resources
analystData analystCreate investigations, analyses, reports
viewerRead-only userView public investigations and reports
serviceService accountAPI access for integrations

Permission Model

class Permission(Enum):
# Investigations
READ_INVESTIGATIONS = "read:investigations"
WRITE_INVESTIGATIONS = "write:investigations"
DELETE_INVESTIGATIONS = "delete:investigations"

# Analyses
READ_ANALYSES = "read:analyses"
WRITE_ANALYSES = "write:analyses"

# Reports
READ_REPORTS = "read:reports"
WRITE_REPORTS = "write:reports"

# Admin
MANAGE_USERS = "manage:users"
VIEW_AUDIT_LOGS = "view:audit"
CONFIGURE_SYSTEM = "configure:system"

Resource-Level Permissions

# Check ownership
if investigation.user_id != current_user.id and not current_user.is_admin:
raise ForbiddenError("You don't have permission to access this resource")

# Check team access
if investigation.team_id not in current_user.team_ids:
raise ForbiddenError("This resource belongs to another team")

Security Headers

Request Headers

Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
X-Client-Version: 1.0.0

Response Headers

X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-Frame-Options: DENY
X-Content-Type-Options: nosniff

Token Management

Token Storage (Client-side)

// Secure storage recommendations
class TokenManager {
// Store in memory for web apps
private accessToken: string;

// Store in secure storage for mobile
async storeRefreshToken(token: string) {
await SecureStore.setItemAsync('refresh_token', token);
}

// Never store in localStorage for sensitive apps
// Use httpOnly cookies when possible
}

Token Validation

def validate_token(token: str) -> TokenPayload:
try:
# Verify signature
payload = jwt.decode(
token,
PUBLIC_KEY,
algorithms=["RS256"],
options={"verify_exp": True}
)

# Check additional claims
if payload.get("iss") != "https://api.cidadao.ai":
raise InvalidTokenError("Invalid issuer")

# Verify token not revoked
if is_token_revoked(payload["jti"]):
raise InvalidTokenError("Token has been revoked")

return TokenPayload(**payload)

except jwt.ExpiredSignatureError:
raise TokenExpiredError("Token has expired")
except jwt.InvalidTokenError as e:
raise InvalidTokenError(f"Invalid token: {str(e)}")

Multi-Factor Authentication (MFA)

TOTP Setup

POST /api/v1/auth/mfa/setup
Authorization: Bearer {token}

Response:
{
"secret": "JBSWY3DPEHPK3PXP",
"qr_code": "data:image/png;base64,...",
"backup_codes": [
"12345678",
"87654321",
...
]
}

MFA Verification

POST /api/v1/auth/mfa/verify
Content-Type: application/json

{
"email": "user@example.com",
"password": "password",
"totp_code": "123456"
}

Session Management

Session Configuration

SESSION_CONFIG = {
"access_token_expire": timedelta(minutes=15),
"refresh_token_expire": timedelta(days=7),
"max_refresh_count": 5,
"concurrent_sessions": 3,
"idle_timeout": timedelta(hours=2)
}

Active Session Monitoring

GET /api/v1/auth/sessions
Authorization: Bearer {token}

Response:
{
"sessions": [
{
"id": "session-1",
"device": "Chrome on Windows",
"ip_address": "192.168.1.1",
"location": "São Paulo, BR",
"last_active": "2025-01-30T10:15:30Z",
"current": true
}
]
}

Security Best Practices

1. Token Security

# Use secure random generation
def generate_token_id():
return secrets.token_urlsafe(32)

# Implement token rotation
def rotate_refresh_token(old_token: str) -> tuple[str, str]:
# Validate old token
payload = validate_token(old_token)

# Generate new tokens
new_access = create_access_token(payload.sub)
new_refresh = create_refresh_token(payload.sub)

# Revoke old refresh token
revoke_token(old_token)

return new_access, new_refresh

2. Rate Limiting

# Implement progressive delays for failed attempts
LOGIN_ATTEMPTS = {
1: 0, # No delay
2: 1, # 1 second
3: 5, # 5 seconds
4: 15, # 15 seconds
5: 60, # 1 minute
6: 300 # 5 minutes (lockout)
}

3. Audit Logging

@audit_log(AuditEventType.AUTHENTICATION_SUCCESS)
async def login(credentials: LoginRequest) -> LoginResponse:
# Authentication logic
pass

@audit_log(AuditEventType.UNAUTHORIZED_ACCESS)
async def handle_unauthorized(request: Request):
# Log unauthorized attempts
pass

OAuth Configuration

Provider Setup

OAUTH_PROVIDERS = {
"google": {
"client_id": os.getenv("GOOGLE_CLIENT_ID"),
"client_secret": os.getenv("GOOGLE_CLIENT_SECRET"),
"authorize_url": "https://accounts.google.com/o/oauth2/v2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"scope": ["openid", "email", "profile"]
},
"github": {
"client_id": os.getenv("GITHUB_CLIENT_ID"),
"client_secret": os.getenv("GITHUB_CLIENT_SECRET"),
"authorize_url": "https://github.com/login/oauth/authorize",
"token_url": "https://github.com/login/oauth/access_token",
"scope": ["user:email"]
}
}

User Approval Flow

Error Handling

Authentication Errors

Error CodeDescriptionHTTP Status
INVALID_CREDENTIALSWrong email/password401
TOKEN_EXPIREDAccess token expired401
TOKEN_INVALIDMalformed or tampered token401
INSUFFICIENT_PERMISSIONSLacks required permissions403
ACCOUNT_LOCKEDToo many failed attempts423
MFA_REQUIREDMust provide MFA code428

Error Response Example

{
"error": {
"code": "TOKEN_EXPIRED",
"message": "Access token has expired",
"details": {
"expired_at": "2025-01-30T10:15:30Z",
"refresh_token_valid": true
},
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}

Testing Authentication

Integration Tests

async def test_full_authentication_flow():
# 1. Register user
response = await client.post("/auth/register", json={
"email": "test@example.com",
"password": "secure-password"
})
assert response.status_code == 201

# 2. Login
response = await client.post("/auth/login", json={
"email": "test@example.com",
"password": "secure-password"
})
tokens = response.json()

# 3. Use access token
response = await client.get(
"/investigations",
headers={"Authorization": f"Bearer {tokens['access_token']}"}
)
assert response.status_code == 200

# 4. Refresh token
response = await client.post("/auth/refresh", json={
"refresh_token": tokens["refresh_token"]
})
assert "access_token" in response.json()