The Supabase plugin provides comprehensive user management capabilities for authentication, session handling, and user data persistence in Unreal Engine projects.
The USupabaseSubsystem serves as the central hub for all user management operations. It’s a Game Instance Subsystem that provides singleton access throughout your application.
Key Features:
// Basic authentication flow
USupabaseManager::LoginWithEmail(this, TEXT("user@example.com"), TEXT("password"));
// Using subsystem directly
USupabaseSubsystem* Subsystem = USupabaseManager::GetSupabaseSubsystem(this);
Subsystem->LoginWithEmail(TEXT("user@example.com"), TEXT("password"));
The core user model contains comprehensive user information:
USTRUCT(BlueprintType)
struct FUser
{
FString Id; // Unique user identifier
FString Aud; // Audience
FString Role; // User role (authenticated, etc.)
FString Email; // Primary email address
FString EmailConfirmedAt; // Email confirmation timestamp
FString Phone; // Phone number
FString ConfirmedAt; // Account confirmation timestamp
FString LastSignInAt; // Last login timestamp
FAppMetadata AppMetadata; // Application-specific metadata
TArray<FIdentity> Identities; // Identity providers
FString CreatedAt; // Account creation timestamp
FString UpdatedAt; // Last update timestamp
bool IsAnonymous; // Anonymous user flag
};
Contains authentication tokens and user data:
USTRUCT(BlueprintType)
struct FTokenResponse
{
FString AccessToken; // JWT access token
FString TokenType; // Token type (usually "bearer")
int32 ExpiresIn; // Token expiration time in seconds
int64 ExpiresAt; // Token expiration timestamp
FString RefreshToken; // Refresh token for token renewal
FUser User; // Complete user information
};
// Simple login
USupabaseManager::LoginWithEmail(this, TEXT("user@email.com"), TEXT("password"));
// Async login with callbacks
UAsyncLogin* LoginTask = UAsyncLogin::LoginWithEmailAsync(this, Email, Password);
LoginTask->OnSuccess.AddDynamic(this, &AMyClass::OnLoginSuccess);
LoginTask->OnFailure.AddDynamic(this, &AMyClass::OnLoginFailure);
// Basic registration
USupabaseManager::RegisterWithEmail(this, TEXT("user@email.com"), TEXT("password"));
// Advanced registration with options
FRegistrationOptions Options;
Options.MinPasswordLength = 12;
Options.bRequireEmailConfirmation = true;
Options.UserMetadata.Add(TEXT("source"), TEXT("game"));
UAsyncRegister* RegisterTask = UAsyncRegister::RegisterWithOptionsAsync(
this, Email, Password, Options
);
The system supports multiple registration modes:
// Simple logout
USupabaseManager::Logout(this);
// Async logout with callbacks
UAsyncLogout* LogoutTask = UAsyncLogout::LogoutAsync(this);
LogoutTask->OnSuccess.AddDynamic(this, &AMyClass::OnLogoutSuccess);
// Manual token refresh
USupabaseManager::RefreshToken(this);
// The system also automatically refreshes tokens when they expire
// Check authentication status
bool bIsAuthenticated = USupabaseManager::IsAuthenticated(this);
// Get current user
FUser CurrentUser = USupabaseManager::GetCurrentUser(this);
The system automatically saves and restores user sessions using USupabaseSessionSave:
// Session data structure
UCLASS()
class USupabaseSessionSave : public USaveGame
{
USupabaseConnection* SupabaseConnection;
FString RefreshToken;
FString Email;
FDateTime LastLogin;
FUser User;
bool bIsLoggedIn;
};
// Load saved session
FSupabaseCredentials Credentials;
FUser User;
bool bLoaded = USupabaseUtils::LoadSupabaseSession(Credentials, User);
// Save current session
USupabaseUtils::SaveSupabaseSession(Credentials, User);
// Clear session
USupabaseUtils::ClearSupabaseSession();
// Connection state events
UPROPERTY(BlueprintAssignable)
FOnConnectionStateChanged OnConnectionStateChanged;
// Authentication state events
UPROPERTY(BlueprintAssignable)
FOnAuthenticationStateChanged OnAuthenticationStateChanged;
The subsystem provides comprehensive event handling:
// Login events
UPROPERTY(BlueprintAssignable)
FSupabaseLoginSuccessfulDelegate OnLoginSuccessful;
UPROPERTY(BlueprintAssignable)
FSupabaseErrorEvent OnLoginFailed;
// Logout events
UPROPERTY(BlueprintAssignable)
FSupabaseSuccessDelegate OnLogoutSuccessful;
UPROPERTY(BlueprintAssignable)
FSupabaseErrorEvent OnLogoutFailed;
void AMyPlayerController::BeginPlay()
{
Super::BeginPlay();
USupabaseSubsystem* Subsystem = USupabaseManager::GetSupabaseSubsystem(this);
if (Subsystem)
{
Subsystem->OnLoginSuccessful.AddDynamic(this, &AMyPlayerController::OnUserLoggedIn);
Subsystem->OnLoginFailed.AddDynamic(this, &AMyPlayerController::OnLoginFailed);
Subsystem->OnAuthenticationStateChanged.AddDynamic(this, &AMyPlayerController::OnAuthStateChanged);
}
}
UFUNCTION()
void OnUserLoggedIn(const FString& Response, const FTokenResponse& TokenResponse)
{
UE_LOG(LogTemp, Log, TEXT("User logged in: %s"), *TokenResponse.User.Email);
// Handle successful login
}
UFUNCTION()
void OnAuthStateChanged(bool bIsAuthenticated, const FUser& User)
{
if (bIsAuthenticated)
{
// User authenticated - update UI, load user data, etc.
}
else
{
// User logged out - clear data, show login screen, etc.
}
}
Application-specific metadata stored in FAppMetadata:
USTRUCT(BlueprintType)
struct FAppMetadata
{
FString Provider; // Authentication provider
TArray<FString> Providers; // Available providers
};
While the base user model is fixed, you can extend user profiles through:
// Example: Custom user profile data
void SaveUserProfile(const FString& UserId, const FString& DisplayName, const FString& AvatarUrl)
{
USupabaseJsonObjectWrapper* ProfileData = USupabaseUtils::MakeSupabaseJson();
ProfileData->SetStringField(TEXT("user_id"), UserId);
ProfileData->SetStringField(TEXT("display_name"), DisplayName);
ProfileData->SetStringField(TEXT("avatar_url"), AvatarUrl);
UAsyncInsert* InsertTask = UAsyncInsert::InsertIntoTableAsync(
this, TEXT("user_profiles"), ProfileData
);
}
The system uses three types of authentication:
// Connection health checks
bool bIsConnected = USupabaseManager::IsConnected(this);
// Test connection
USupabaseManager* Manager = USupabaseManager::GetSupabaseSubsystem(this);
Manager->TestConnection();
// Preferred approach for basic operations
USupabaseManager::LoginWithEmail(this, Email, Password);
USupabaseManager::Logout(this);
// Use async classes when you need detailed control
UAsyncLogin* LoginTask = UAsyncLogin::LoginWithEmailAsync(this, Email, Password);
LoginTask->OnSuccess.AddDynamic(this, &AMyClass::HandleLoginSuccess);
// Always listen for authentication state changes
void AMyGameMode::BeginPlay()
{
Super::BeginPlay();
USupabaseSubsystem* Subsystem = USupabaseManager::GetSupabaseSubsystem(this);
Subsystem->OnAuthenticationStateChanged.AddDynamic(this, &AMyGameMode::OnAuthChanged);
}
UFUNCTION()
void OnLoginFailed(const FString& Error)
{
if (Error.Contains(TEXT("Invalid credentials")))
{
// Show "wrong username/password" message
}
else if (Error.Contains(TEXT("Email not confirmed")))
{
// Show "please confirm your email" message
}
else
{
// Show generic error message
}
}
// Use built-in validation utilities
bool bValidEmail = UAsyncRegister::ValidateEmailFormat(EmailInput);
bool bValidPassword = UAsyncRegister::ValidatePasswordStrength(PasswordInput, Options);
BeginPlay()USupabaseConnection assetUSupabaseManager::InitializeSupabase() with your connectionUSupabaseSessionSave is being created properly// Enable debug logging for troubleshooting
USupabaseSubsystem* Subsystem = USupabaseManager::GetSupabaseSubsystem(this);
Subsystem->EnableDebugLogging(true);
// Log connection information
USupabaseUtils::LogSupabaseConnectionInfo(this);
The Supabase plugin provides production-ready OAuth authentication with support for multiple providers and security flows.
The system supports multiple OAuth providers through the EAuthProvider enum:
UENUM(BlueprintType)
enum class EAuthProvider : uint8
{
Email UMETA(DisplayName = "Email"),
Google UMETA(DisplayName = "Google"),
Facebook UMETA(DisplayName = "Facebook"),
GitHub UMETA(DisplayName = "GitHub"),
Twitter UMETA(DisplayName = "Twitter")
};
OAuth providers are configured using UAuthenticationProvider data assets:
UCLASS(BlueprintType)
class UAuthenticationProvider : public UPrimaryDataAsset
{
UPROPERTY(EditAnywhere, BlueprintReadWrite)
EAuthProvider Provider;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString ClientID;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString ClientSecret;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString RedirectURI;
};
The system supports three industry-standard OAuth flows:
Proof Key for Code Exchange - Most secure for mobile and desktop applications:
UAsyncOAuth* OAuthTask = UAsyncOAuth::OAuthAsync(
this,
AuthProvider,
EOAuthFlowType::PKCE
);
OAuthTask->OnSuccess.AddDynamic(this, &AMyClass::OnOAuthSuccess);
OAuthTask->OnFailure.AddDynamic(this, &AMyClass::OnOAuthFailure);
Key Features:
Traditional server-side OAuth flow with client secret:
UAsyncOAuth* AuthCodeTask = UAsyncOAuth::OAuthAsync(
this,
AuthProvider,
EOAuthFlowType::AuthorizationCode
);
Requirements:
Direct token retrieval (for single-page applications):
UAsyncOAuth* ImplicitTask = UAsyncOAuth::OAuthAsync(
this,
AuthProvider,
EOAuthFlowType::Implicit
);
Note: Requires manual browser navigation and callback handling.
// Setup authentication provider (created as Data Asset)
UAuthenticationProvider* GoogleProvider = LoadObject<UAuthenticationProvider>(
nullptr,
TEXT("/Game/Auth/GoogleAuthProvider.GoogleAuthProvider")
);
// Start OAuth flow
UAsyncOAuth* OAuthTask = UAsyncOAuth::OAuthAsync(this, GoogleProvider, EOAuthFlowType::PKCE);
OAuthTask->OnSuccess.AddDynamic(this, &AMyPlayerController::OnOAuthSuccess);
OAuthTask->OnFailure.AddDynamic(this, &AMyPlayerController::OnOAuthFailure);
TArray<FString> Scopes;
Scopes.Add(TEXT("email"));
Scopes.Add(TEXT("profile"));
Scopes.Add(TEXT("openid"));
UAsyncOAuth* ScopedTask = UAsyncOAuth::OAuthWithScopesAsync(
this,
AuthProvider,
Scopes,
EOAuthFlowType::PKCE
);
FString CustomState = TEXT("secure_random_state_12345");
UAsyncOAuth* SecureTask = UAsyncOAuth::OAuthSecureAsync(
this,
AuthProvider,
CustomState,
Scopes,
EOAuthFlowType::PKCE
);
For desktop applications, you typically need to handle browser interaction:
void AMyPlayerController::StartOAuthFlow()
{
UAsyncOAuth* OAuthTask = UAsyncOAuth::OAuthAsync(this, AuthProvider, EOAuthFlowType::PKCE);
// Get the authorization URL
FString AuthURL = OAuthTask->GetAuthorizationURL();
// Open in system browser (Windows example)
FString BrowserCommand = FString::Printf(TEXT("start %s"), *AuthURL);
FPlatformProcess::ExecProcess(TEXT("cmd.exe"), *FString::Printf(TEXT("/c %s"), *BrowserCommand), nullptr, nullptr, nullptr);
// Store reference for callback handling
CurrentOAuthTask = OAuthTask;
// In a real implementation, you would listen for the redirect callback
// and extract the authorization code, then call:
// OAuthTask->HandleAuthorizationResponse(AuthCode, State);
}
For in-game OAuth, use the WebBrowserWidget plugin:
// In your widget's blueprint or C++
void UOAuthWidget::OpenOAuthBrowser()
{
if (WebBrowserWidget)
{
FString AuthURL = OAuthTask->GetAuthorizationURL();
WebBrowserWidget->LoadURL(AuthURL);
// Bind to URL change events to detect redirect
WebBrowserWidget->OnUrlChanged.AddDynamic(this, &UOAuthWidget::OnBrowserUrlChanged);
}
}
UFUNCTION()
void UOAuthWidget::OnBrowserUrlChanged(const FText& NewURL)
{
FString URLString = NewURL.ToString();
// Check if this is the redirect URL
if (URLString.StartsWith(AuthProvider->RedirectURI))
{
// Parse authorization code from URL
FString AuthCode = ExtractAuthCodeFromURL(URLString);
FString State = ExtractStateFromURL(URLString);
// Complete OAuth flow
if (CurrentOAuthTask)
{
CurrentOAuthTask->HandleAuthorizationResponse(AuthCode, State);
}
// Hide browser widget
WebBrowserWidget->SetVisibility(ESlateVisibility::Hidden);
}
}
// Authorization URL: https://accounts.google.com/oauth2/v2/auth
// Token URL: https://oauth2.googleapis.com/token
// Required Scopes: email, profile, openid
// In your Authentication Provider asset:
Provider = EAuthProvider::Google
ClientID = "your-google-client-id.googleusercontent.com"
RedirectURI = "https://yourapp.com/auth/callback" // or "myapp://oauth/callback" for native
// Authorization URL: https://github.com/login/oauth/authorize
// Token URL: https://github.com/login/oauth/access_token
// Common Scopes: user:email, read:user
Provider = EAuthProvider::GitHub
ClientID = "your-github-client-id"
RedirectURI = "https://yourapp.com/auth/github/callback"
// Authorization URL: https://www.facebook.com/v18.0/dialog/oauth
// Token URL: https://graph.facebook.com/v18.0/oauth/access_token
// Common Scopes: email, public_profile
Provider = EAuthProvider::Facebook
ClientID = "your-facebook-app-id"
RedirectURI = "https://yourapp.com/auth/facebook/callback"
The OAuth implementation includes built-in CSRF protection:
// State parameter is automatically generated and validated
FString State = OAuthTask->GenerateState(); // Generates unique GUID
bool bValid = OAuthTask->ValidateState(ReceivedState); // Validates on callback
For enhanced security, PKCE parameters are automatically generated:
// Automatic PKCE parameter generation
FString CodeVerifier = GenerateCodeVerifier(); // 32-byte random string
FString CodeChallenge = GenerateCodeChallenge(CodeVerifier); // SHA256 hash (base64url)
The system validates redirect URIs for security:
// Supported redirect URI formats:
// - https://yourapp.com/callback
// - http://localhost:3000/callback (development)
// - myapp://oauth/callback (native apps)
UFUNCTION()
void OnOAuthSuccess(const FString& AccessToken, const FString& RefreshToken)
{
UE_LOG(LogTemp, Log, TEXT("OAuth successful! Access Token: %s"), *AccessToken);
// Store tokens in your connection
if (USupabaseSubsystem* Subsystem = USupabaseManager::GetSupabaseSubsystem(this))
{
USupabaseConnection* Connection = Subsystem->GetActiveConnection();
if (Connection)
{
Connection->SupabaseCredentials.UserAuthenticatedKey = AccessToken;
Connection->SupabaseCredentials.RefreshToken = RefreshToken;
Connection->bIsAuthenticated = true;
}
}
// Trigger authentication state change
OnUserAuthenticated.Broadcast();
}
UFUNCTION()
void OnOAuthFailure(const FString& ErrorMessage)
{
UE_LOG(LogTemp, Warning, TEXT("OAuth failed: %s"), *ErrorMessage);
// Handle specific error cases
if (ErrorMessage.Contains(TEXT("State parameter mismatch")))
{
// Possible CSRF attack - show security warning
ShowSecurityWarning();
}
else if (ErrorMessage.Contains(TEXT("User denied")))
{
// User cancelled OAuth - return to login screen
ShowLoginScreen();
}
else
{
// Generic error - show retry option
ShowOAuthError(ErrorMessage);
}
}
// Check OAuth status
bool bInProgress = OAuthTask->IsOAuthInProgress();
// Cancel OAuth flow
OAuthTask->CancelOAuth();
// Get current authorization URL
FString CurrentURL = OAuthTask->GetAuthorizationURL();
For custom providers or special requirements:
// Advanced OAuth with direct connection
UAsyncOAuth* AdvancedTask = UAsyncOAuth::OAuthAdvanced(
this,
CustomConnection,
AuthProvider,
EOAuthFlowType::PKCE
);
Always prefer PKCE over Authorization Code for native applications:
// Preferred approach
UAsyncOAuth::OAuthAsync(this, Provider, EOAuthFlowType::PKCE);
// Avoid for native apps (requires client secret)
UAsyncOAuth::OAuthAsync(this, Provider, EOAuthFlowType::AuthorizationCode);
Always use secure state parameters to prevent CSRF attacks:
// Generate cryptographically secure state
FString SecureState = FGuid::NewGuid().ToString();
UAsyncOAuth::OAuthSecureAsync(this, Provider, SecureState, Scopes);
Configure appropriate redirect URIs for your platform:
// For packaged games
RedirectURI = TEXT("myapp://oauth/callback");
// For development/testing
RedirectURI = TEXT("http://localhost:8080/callback");
// For web-based games
RedirectURI = TEXT("https://yourgame.com/auth/callback");
Never log or expose tokens in production:
void OnOAuthSuccess(const FString& AccessToken, const FString& RefreshToken)
{
// ❌ Don't do this in production
UE_LOG(LogTemp, Log, TEXT("Access Token: %s"), *AccessToken);
// ✅ Store securely
StoreTokensSecurely(AccessToken, RefreshToken);
}
OAuth tokens can expire, handle refresh appropriately:
void RefreshOAuthToken()
{
if (USupabaseSubsystem* Subsystem = USupabaseManager::GetSupabaseSubsystem(this))
{
// Use the built-in refresh token mechanism
Subsystem->RefreshToken();
}
}
UCLASS()
class MYGAME_API UOAuthLoginWidget : public UUserWidget
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
void StartGoogleOAuth();
UFUNCTION(BlueprintCallable)
void StartGitHubOAuth();
protected:
UPROPERTY(meta = (BindWidget))
class UWebBrowser* OAuthBrowser;
UPROPERTY(EditAnywhere, Category = "OAuth")
UAuthenticationProvider* GoogleProvider;
UPROPERTY(EditAnywhere, Category = "OAuth")
UAuthenticationProvider* GitHubProvider;
UFUNCTION()
void OnOAuthSuccess(const FString& AccessToken, const FString& RefreshToken);
UFUNCTION()
void OnOAuthFailure(const FString& ErrorMessage);
private:
UPROPERTY()
UAsyncOAuth* CurrentOAuthTask;
};
void UOAuthLoginWidget::StartGoogleOAuth()
{
CurrentOAuthTask = UAsyncOAuth::OAuthAsync(this, GoogleProvider, EOAuthFlowType::PKCE);
CurrentOAuthTask->OnSuccess.AddDynamic(this, &UOAuthLoginWidget::OnOAuthSuccess);
CurrentOAuthTask->OnFailure.AddDynamic(this, &UOAuthLoginWidget::OnOAuthFailure);
// Show OAuth URL in browser widget
FString AuthURL = CurrentOAuthTask->GetAuthorizationURL();
OAuthBrowser->LoadURL(AuthURL);
OAuthBrowser->SetVisibility(ESlateVisibility::Visible);
}
// Login screen with proper error handling
UCLASS()
class MYGAME_API ULoginWidget : public UUserWidget
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
void AttemptLogin();
UFUNCTION()
void OnLoginSuccess(const FString& Response, const FTokenResponse& TokenResponse);
UFUNCTION()
void OnLoginFailure(const FString& Error);
protected:
UPROPERTY(meta = (BindWidget))
class UEditableTextBox* EmailTextBox;
UPROPERTY(meta = (BindWidget))
class UEditableTextBox* PasswordTextBox;
UPROPERTY(meta = (BindWidget))
class UTextBlock* ErrorTextBlock;
};
void ULoginWidget::AttemptLogin()
{
FString Email = EmailTextBox->GetText().ToString();
FString Password = PasswordTextBox->GetText().ToString();
UAsyncLogin* LoginTask = UAsyncLogin::LoginWithEmailAsync(this, Email, Password);
LoginTask->OnSuccess.AddDynamic(this, &ULoginWidget::OnLoginSuccess);
LoginTask->OnFailure.AddDynamic(this, &ULoginWidget::OnLoginFailure);
}
// Extended user profile management
UCLASS()
class MYGAME_API UUserProfileManager : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
void LoadUserProfile(const FString& UserId);
UFUNCTION(BlueprintCallable)
void SaveUserProfile(const FString& UserId, const FString& DisplayName);
private:
UFUNCTION()
void OnProfileLoaded(const FString& Response, const FJsonObjectWrapper& JsonResponse);
UFUNCTION()
void OnProfileSaved(const FString& Response, bool bSuccess);
};
This User Management system provides a robust foundation for authentication and user data management in your Unreal Engine project with Supabase integration.