Authentication Guide
RedMist uses OAuth 2.0 for authentication and authorization.
Authentication Flow
Client Credentials Flow (Recommended for Services)
The client credentials flow is used for machine-to-machine authentication.
Step 1: Request Token
Endpoint: POST https://auth.redmist.racing/realms/redmist/protocol/openid-connect/token
Headers:
Content-Type: application/x-www-form-urlencoded
| Parameter | Value | Description | 
|---|---|---|
| grant_type | client_credentials | OAuth2 grant type | 
| client_id | YOUR_CLIENT_ID | Your client identifier | 
| client_secret | YOUR_SECRET | Your client secret | 
Example Request:
curl -X POST "https://auth.redmist.racing/realms/redmist/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=relay-myorg" \
  -d "client_secret=abc123xyz"
Step 2: Receive Token
Response:
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 300,
  "refresh_expires_in": 0,
  "token_type": "Bearer",
  "not-before-policy": 0,
  "scope": "profile email"
}
Step 3: Use Token
Include the token in the Authorization header of all API requests:
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Token Types
Service Account Tokens
- Used for server-to-server communication
- Obtained via client credentials flow
- No user context
- Typically expires in 5 minutes
User Tokens
- Obtained via authorization code flow (web applications)
- Contains user identity and claims
- Supports refresh tokens
Code Examples
JavaScript/Node.js
async function getAccessToken() {
    const params = new URLSearchParams();
    params.append("grant_type", "client_credentials");
    params.append("client_id", process.env.CLIENT_ID);
    params.append("client_secret", process.env.CLIENT_SECRET);
    const response = await fetch(
        "https://auth.redmist.racing/realms/redmist/protocol/openid-connect/token",
        {
            method: "POST",
            headers: {
                "Content-Type": "application/x-www-form-urlencoded"
            },
            body: params
        }
    );
    const data = await response.json();
    return data.access_token;
}
// Use with API
async function getEvents() {
    const token = await getAccessToken();
    
    const response = await fetch(
        "https://api.redmist.racing/Events/LoadLiveEvents",
        {
            headers: {
                "Authorization": `Bearer ${token}`
            }
        }
    );
    
    return await response.json();
}
Python
import requests
import os
def get_access_token():
    """Get access token using client credentials"""
    response = requests.post(
        "https://auth.redmist.racing/realms/redmist/protocol/openid-connect/token",
        data={
            "grant_type": "client_credentials",
            "client_id": os.environ["CLIENT_ID"],
            "client_secret": os.environ["CLIENT_SECRET"]
        }
    )
    response.raise_for_status()
    return response.json()["access_token"]
def get_events():
    """Get live events using authenticated API"""
    token = get_access_token()
    
    response = requests.get(
        "https://api.redmist.racing/Events/LoadLiveEvents",
        headers={"Authorization": f"Bearer {token}"}
    )
    response.raise_for_status()
    return response.json()
C#
using System.Net.Http;
using System.Net.Http.Headers;
public class RedMistAuthClient
{
    private readonly HttpClient _httpClient;
    private readonly string _clientId;
    private readonly string _clientSecret;
    private string? _accessToken;
    private DateTime _tokenExpiry;
    public RedMistAuthClient(string clientId, string clientSecret)
    {
        _httpClient = new HttpClient();
        _clientId = clientId;
        _clientSecret = clientSecret;
    }
    public async Task<string> GetAccessTokenAsync()
    {
        // Return cached token if still valid
        if (_accessToken != null && DateTime.UtcNow < _tokenExpiry)
        {
            return _accessToken;
        }
        var request = new HttpRequestMessage(HttpMethod.Post, 
            "https://auth.redmist.racing/realms/redmist/protocol/openid-connect/token");
        var formData = new Dictionary<string, string>
        {
            ["grant_type"] = "client_credentials",
            ["client_id"] = _clientId,
            ["client_secret"] = _clientSecret
        };
        request.Content = new FormUrlEncodedContent(formData);
        var response = await _httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();
        var result = await response.Content.ReadFromJsonAsync<TokenResponse>();
        
        _accessToken = result.AccessToken;
        _tokenExpiry = DateTime.UtcNow.AddSeconds(result.ExpiresIn - 30); // Refresh 30s early
        return _accessToken;
    }
    public async Task<List<EventListSummary>> GetLiveEventsAsync()
    {
        var token = await GetAccessTokenAsync();
        
        var request = new HttpRequestMessage(HttpMethod.Get, 
            "https://api.redmist.racing/Events/LoadLiveEvents");
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        var response = await _httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<List<EventListSummary>>();
    }
    private record TokenResponse(
        [property: JsonPropertyName("access_token")] string AccessToken,
        [property: JsonPropertyName("expires_in")] int ExpiresIn
    );
}
Token Management
Token Expiration
Tokens expire after 5 minutes (300 seconds). Implement token caching and refresh:
class TokenManager {
    constructor(clientId, clientSecret) {
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.token = null;
        this.expiry = null;
    }
    async getToken() {
        // Check if token is still valid
        if (this.token && this.expiry > Date.now()) {
            return this.token;
        }
        // Request new token
        const response = await fetch(
            "https://auth.redmist.racing/realms/redmist/protocol/openid-connect/token",
            {
                method: "POST",
                headers: { "Content-Type": "application/x-www-form-urlencoded" },
                body: new URLSearchParams({
                    grant_type: "client_credentials",
                    client_id: this.clientId,
                    client_secret: this.clientSecret
                })
            }
        );
        const data = await response.json();
        this.token = data.access_token;
        this.expiry = Date.now() + (data.expires_in - 30) * 1000; // Refresh 30s early
        return this.token;
    }
}
SignalR Authentication
For SignalR connections, provide a token factory:
const connection = new signalR.HubConnectionBuilder()
    .withUrl("https://api.redmist.racing/status/event-status", {
        accessTokenFactory: async () => await tokenManager.getToken()
    })
    .withAutomaticReconnect()
    .build();
Authorization
Roles and Claims
Tokens include role claims that determine access:
Service Account Roles:
- relay-svc- Relay client permissions
- org-admin- Organization administration
User Roles:
- user- Basic user access
- admin- Administrative access
Endpoint Authorization
Different endpoints require different permissions:
| Endpoint | Permission | Role Required | 
|---|---|---|
| GET /Events/LoadLiveEvents | Public | None | 
| GET /Events/LoadEvent | Authenticated | Any | 
| POST /Event/SaveNewEvent | Organization Owner | org-admin | 
| GET /Organization/LoadRelayConnection | Organization Member | Any in org | 
Security Best Practices
1. Secure Credential Storage
- Never commit credentials to source control
- Use environment variables or secure vaults
- Rotate secrets regularly
2. Token Security
- Always use HTTPS
- Don't log tokens
- Clear tokens on logout
- Implement token refresh
3. Error Handling
async function apiCall() {
    try {
        const response = await fetch(url, {
            headers: { Authorization: `Bearer ${token}` }
        });
        if (response.status === 401) {
            // Token expired, refresh and retry
            token = await getNewToken();
            return apiCall();
        }
        if (!response.ok) {
            throw new Error(`API error: ${response.status}`);
        }
        return await response.json();
    } catch (error) {
        console.error("API call failed:", error);
        throw error;
    }
}
Troubleshooting
Invalid Credentials
Error: 401 Unauthorized
- Verify client_id and client_secret are correct
- Check if client is enabled in Keycloak
- Ensure using correct realm
Token Expired
Error: 401 Unauthorized on API call
- Implement token refresh logic
- Check token expiration time
- Verify system clock is accurate
CORS Issues
Error: CORS policy blocking request
- Ensure using HTTPS
- Check if origin is allowed
- Use server-side proxy if needed