// ============================================== // AuthManager.cs - Unity Auth Handler // ============================================== // Handles JWT token for Keycloak authentication. // Supports token via URL parameter or PostMessage from parent frame. using System; using System.Runtime.InteropServices; using UnityEngine; namespace BreakpilotDrive { public class AuthManager : MonoBehaviour { public static AuthManager Instance { get; private set; } [Header("Auth Configuration")] [SerializeField] private bool requireAuth = false; [SerializeField] private string devUserId = "dev-user-123"; // Current auth state private string jwtToken; private string userId; private bool isAuthenticated; // Events public event Action OnAuthenticated; public event Action OnAuthFailed; // JavaScript interface for WebGL #if UNITY_WEBGL && !UNITY_EDITOR [DllImport("__Internal")] private static extern string GetTokenFromURL(); [DllImport("__Internal")] private static extern string GetTokenFromParent(); [DllImport("__Internal")] private static extern void RequestTokenFromParent(); #endif void Awake() { if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); } } void Start() { InitializeAuth(); } // ============================================== // Initialization // ============================================== private void InitializeAuth() { // Development mode - use dev user if (!requireAuth || Application.isEditor) { SetDevUser(); return; } #if UNITY_WEBGL && !UNITY_EDITOR // Try URL parameter first string urlToken = GetTokenFromURL(); if (!string.IsNullOrEmpty(urlToken)) { SetToken(urlToken); return; } // Try parent frame (iframe scenario) RequestTokenFromParent(); #else // Non-WebGL builds use dev user SetDevUser(); #endif } private void SetDevUser() { userId = devUserId; isAuthenticated = true; jwtToken = null; Debug.Log($"[Auth] Development mode - User: {userId}"); OnAuthenticated?.Invoke(userId); } // ============================================== // Token Management // ============================================== public void SetToken(string token) { if (string.IsNullOrEmpty(token)) { OnAuthFailed?.Invoke("Empty token"); return; } jwtToken = token; userId = ExtractUserIdFromToken(token); if (!string.IsNullOrEmpty(userId)) { isAuthenticated = true; Debug.Log($"[Auth] Authenticated - User: {userId}"); OnAuthenticated?.Invoke(userId); } else { OnAuthFailed?.Invoke("Could not extract user ID from token"); } } // Called from JavaScript via SendMessage public void ReceiveTokenFromJS(string token) { Debug.Log("[Auth] Received token from JavaScript"); SetToken(token); } // Called from JavaScript if auth fails public void AuthFailedFromJS(string error) { Debug.LogWarning($"[Auth] JavaScript auth failed: {error}"); // Fall back to dev user in development if (!requireAuth) { SetDevUser(); } else { OnAuthFailed?.Invoke(error); } } private string ExtractUserIdFromToken(string token) { try { // JWT has 3 parts: header.payload.signature string[] parts = token.Split('.'); if (parts.Length != 3) return null; // Decode payload (base64) string payload = parts[1]; // Fix base64 padding int padding = 4 - (payload.Length % 4); if (padding < 4) payload += new string('=', padding); // Replace URL-safe chars payload = payload.Replace('-', '+').Replace('_', '/'); byte[] bytes = Convert.FromBase64String(payload); string json = System.Text.Encoding.UTF8.GetString(bytes); // Simple JSON parsing for "sub" claim var claims = JsonUtility.FromJson(json); return claims?.sub; } catch (Exception e) { Debug.LogWarning($"[Auth] Token parsing failed: {e.Message}"); return null; } } [Serializable] private class JWTPayload { public string sub; public string email; public string name; public long exp; public long iat; } // ============================================== // Public API // ============================================== public bool IsAuthenticated => isAuthenticated; public string UserId => userId; public string Token => jwtToken; public bool RequiresAuth => requireAuth; public string GetAuthHeader() { if (string.IsNullOrEmpty(jwtToken)) return null; return $"Bearer {jwtToken}"; } public void Logout() { jwtToken = null; userId = null; isAuthenticated = false; Debug.Log("[Auth] Logged out"); } // Check if token is expired public bool IsTokenExpired() { if (string.IsNullOrEmpty(jwtToken)) return true; try { string[] parts = jwtToken.Split('.'); if (parts.Length != 3) return true; string payload = parts[1]; int padding = 4 - (payload.Length % 4); if (padding < 4) payload += new string('=', padding); payload = payload.Replace('-', '+').Replace('_', '/'); byte[] bytes = Convert.FromBase64String(payload); string json = System.Text.Encoding.UTF8.GetString(bytes); var claims = JsonUtility.FromJson(json); if (claims?.exp > 0) { var expTime = DateTimeOffset.FromUnixTimeSeconds(claims.exp).UtcDateTime; return DateTime.UtcNow > expTime; } } catch { } return false; } } }