Rystem.Authentication.Social helps you to integrate with new .Net Identity system and social logins.
$ dotnet add package Rystem.Authentication.Social.BlazorBlazor UI components for social authentication (Blazor Server and WebAssembly) with built-in PKCE support for secure OAuth 2.0 flows.
All social providers now support MAUI Blazor Hybrid! Configure platform-specific OAuth redirect URIs for seamless authentication across Web (Blazor Server/WASM), iOS (MAUI), and Android (MAUI).
| Provider | Blazor Server | Blazor WASM | MAUI iOS | MAUI Android | PKCE Support |
|---|---|---|---|---|---|
| Microsoft | ✅ | ✅ | ✅ | ✅ | ✅ |
| ✅ | ✅ | ✅ | ✅ | - |
More providers coming soon for Blazor
msauth:// for iOS, myapp:// for Android)AppActions// Program.cs or MauiProgram.cs
builder.Services.AddSocialLoginUI(x =>
{
x.ApiUrl = "https://api.yourdomain.com";
// Platform configuration (auto-detects if not specified)
x.Platform = new PlatformConfig
{
Type = PlatformType.Auto, // Auto-detect Web/iOS/Android
// Smart redirect path detection:
// - Contains "://" -> Complete URI (mobile deep links: msauth://, myapp://)
// - Starts with "/" -> Relative path (web, auto-detects domain)
// - Empty/null -> Default "/account/login"
#if IOS
RedirectPath = "msauth://com.yourapp.bundle/auth", // Complete URI for iOS
#elif ANDROID
RedirectPath = "myapp://oauth/callback", // Complete URI for Android
#else
RedirectPath = "/account/login", // Relative path for web
#endif
LoginMode = LoginMode.Redirect
};
x.Microsoft.ClientId = builder.Configuration["Microsoft:ClientId"];
x.Google.ClientId = builder.Configuration["Google:ClientId"];
});
iOS Deep Link Configuration (Platforms/iOS/Info.plist):
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array><string>msauth</string></array>
<key>CFBundleURLName</key>
<string>com.yourapp.bundle</string>
</dict>
</array>
Android Deep Link Configuration (Platforms/Android/AndroidManifest.xml):
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="oauth" />
</intent-filter>
📖 Full Migration Guide: See PLATFORM_SUPPORT.md for detailed setup instructions, OAuth provider configuration, and troubleshooting.
dotnet add package Rystem.Authentication.Social.Blazor
Add to your App.razor (in <head> or before </body>):
<script src="_content/Rystem.Authentication.Social.Blazor/socialauthentications.js"></script>
var builder = WebApplication.CreateBuilder(args);
// Add Razor components
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(); // or .AddInteractiveWebAssemblyComponents()
// Configure social login UI
builder.Services.AddSocialLoginUI(x =>
{
x.ApiUrl = "https://localhost:7017"; // Your API server URL
// Configure OAuth providers (only ClientId needed for client-side)
x.Microsoft.ClientId = builder.Configuration["SocialLogin:Microsoft:ClientId"];
x.Google.ClientId = builder.Configuration["SocialLogin:Google:ClientId"];
x.Facebook.ClientId = builder.Configuration["SocialLogin:Facebook:ClientId"];
x.GitHub.ClientId = builder.Configuration["SocialLogin:GitHub:ClientId"];
// Add other providers as needed
});
// Optional: Configure repository with automatic authorization headers
builder.Services.AddRepository<User, Guid>(repositoryBuilder =>
{
repositoryBuilder.WithApiClient(apiBuilder =>
{
apiBuilder.WithHttpClient("https://localhost:7017")
.WithDefaultRetryPolicy();
});
});
// Add authorization interceptor to inject Bearer tokens automatically
builder.Services.AddDefaultAuthorizationInterceptorForApiHttpClient();
var app = builder.Build();
// Configure middleware
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode(); // or .AddInteractiveWebAssemblyRenderMode()
app.Run();
Replace default router with SocialAuthenticationRouter:
<SocialAuthenticationRouter
AppAssembly="typeof(Program).Assembly"
DefaultLayout="typeof(Layout.MainLayout)">
</SocialAuthenticationRouter>
This automatically:
/account/login?code=...)SocialUserWrapper parameter to all pages@page "/login"
@using Rystem.Authentication.Social.Blazor
<h3>Login</h3>
<SocialLogin />
@code {
// Component automatically renders all configured provider buttons
}
@page "/dashboard"
<h3>Welcome, @SocialUser?.User?.Username</h3>
@if (SocialUser?.User != null)
{
<p>You are logged in as @SocialUser.User.Username</p>
<SocialLogout />
}
else
{
<p>Please <a href="/login">login</a></p>
}
@code {
[CascadingParameter(Name = "SocialUser")]
public SocialUserWrapper? SocialUser { get; set; }
}
The library automatically implements PKCE for Microsoft OAuth:
Code Verifier Generation: When user clicks Microsoft login button
var codeVerifier = PkceGenerator.GenerateCodeVerifier(); // 43-128 chars random string
var codeChallenge = PkceGenerator.GenerateCodeChallenge(codeVerifier); // SHA256 hash
Local Storage: Stores code_verifier for callback retrieval
await LocalStorage.SetItemAsync("microsoft_code_verifier", codeVerifier);
OAuth Request: Sends code_challenge with S256 method
https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize
?client_id={clientId}
&response_type=code
&redirect_uri={redirectUri}
&code_challenge={codeChallenge}
&code_challenge_method=S256
Token Exchange: Sends code_verifier to API server
POST /api/Authentication/Social/Token?provider=Microsoft&code={code}
Body: { "code_verifier": "original-verifier" }
Cleanup: Removes verifier from localStorage after use
For custom implementations:
@inject SocialLoginLocalStorageService LocalStorage
private async Task CustomLoginAsync()
{
// Generate PKCE values
var codeVerifier = PkceGenerator.GenerateCodeVerifier();
var codeChallenge = PkceGenerator.GenerateCodeChallenge(codeVerifier);
// Store for later retrieval
await LocalStorage.SetItemAsync("custom_code_verifier", codeVerifier);
// Build OAuth URL with code_challenge
var authUrl = $"https://oauth.provider.com/authorize?code_challenge={codeChallenge}&code_challenge_method=S256";
NavigationManager.NavigateTo(authUrl);
}
Renders all configured provider buttons:
<SocialLogin />
<MicrosoftButton />
<GoogleButton />
<FacebookButton />
<GitHubButton />
<SocialLogin>
<MicrosoftButton />
<GoogleButton />
<GitHubButton />
</SocialLogin>
<SocialLogout />
@code {
[CascadingParameter(Name = "SocialUser")]
public SocialUserWrapper? SocialUser { get; set; }
private async Task LogoutAsync()
{
if (SocialUser != null)
{
await SocialUser.LogoutAsync(refreshPage: false);
NavigationManager.NavigateTo("/login");
}
}
}
The library now supports platform-specific configuration for Web, iOS (MAUI), and Android (MAUI):
builder.Services.AddSocialLoginUI(x =>
{
x.ApiUrl = "https://yourdomain.com";
// Platform configuration
x.Platform = new PlatformConfig
{
Type = PlatformType.Auto, // Auto-detect (Web/iOS/Android)
// Platform-specific redirect URIs
RedirectUri = null, // null = use NavigationManager.BaseUri for web
// For mobile apps (MAUI), set explicit redirect URI:
// RedirectUri = "msauth://com.yourapp.bundle/auth", // iOS
// RedirectUri = "myapp://oauth/callback", // Android
RedirectPath = "/account/login", // Path appended to RedirectUri
LoginMode = LoginMode.Redirect // Only Redirect supported currently
};
// OAuth providers
x.Microsoft.ClientId = "your-client-id";
x.Google.ClientId = "your-client-id";
});
For Blazor Hybrid in .NET MAUI, configure deep links:
// Program.cs or MauiProgram.cs
builder.Services.AddSocialLoginUI(x =>
{
x.ApiUrl = "https://api.yourdomain.com";
// Detect platform and configure accordingly
x.Platform = new PlatformConfig
{
Type = PlatformType.Auto, // Auto-detects iOS or Android
#if IOS
RedirectUri = "msauth://com.keyserdsoze.fantasoccer/auth",
#elif ANDROID
RedirectUri = "fantasoccer://oauth/callback",
#else
RedirectUri = null, // Web: use NavigationManager.BaseUri
#endif
RedirectPath = "/account/login",
LoginMode = LoginMode.Redirect
};
x.Microsoft.ClientId = builder.Configuration["Microsoft:ClientId"];
});
iOS Configuration (Platforms/iOS/Info.plist):
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>msauth</string>
</array>
<key>CFBundleURLName</key>
<string>com.keyserdsoze.fantasoccer</string>
</dict>
</array>
Android Configuration (Platforms/Android/AndroidManifest.xml):
<activity android:name="com.microsoft.identity.client.BrowserTabActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="fantasoccer"
android:host="oauth"
android:path="/callback" />
</intent-filter>
</activity>
MAUI App Deep Link Handler (MauiProgram.cs):
builder.Services.AddSingleton<IDeepLinkHandler, DeepLinkHandler>();
// DeepLinkHandler.cs
public class DeepLinkHandler : IDeepLinkHandler
{
private readonly NavigationManager _navigationManager;
public DeepLinkHandler(NavigationManager navigationManager)
{
_navigationManager = navigationManager;
}
public void HandleDeepLink(string uri)
{
// OAuth callback from mobile OAuth flow
if (uri.Contains("code=") && uri.Contains("state="))
{
// Extract query parameters and navigate to callback page
var parsedUri = new Uri(uri);
var code = HttpUtility.ParseQueryString(parsedUri.Query).Get("code");
var state = HttpUtility.ParseQueryString(parsedUri.Query).Get("state");
_navigationManager.NavigateTo($"/account/login?code={code}&state={state}");
}
}
}
Use built-in utilities for platform detection:
@inject IJSRuntime JSRuntime
@code {
private PlatformType _currentPlatform;
protected override async Task OnInitializedAsync()
{
// Async platform detection
_currentPlatform = await PlatformDetector.DetectPlatformAsync(JSRuntime);
// Or synchronous (compile-time detection)
_currentPlatform = PlatformDetector.DetectPlatformSync();
// Check if mobile
if (PlatformDetector.IsMobilePlatform(_currentPlatform))
{
// Configure mobile-specific behavior
}
// Check if Blazor Hybrid (MAUI)
if (PlatformDetector.IsBlazorHybrid())
{
// MAUI-specific initialization
}
}
}
Currently, only Redirect mode is supported in Blazor:
x.LoginMode = LoginMode.Redirect; // Default
Note: Popup mode will be added in a future release. For now, all OAuth flows use redirect navigation.
// MauiProgram.cs
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
// Blazor components
builder.Services.AddMauiBlazorWebView();
// Detect platform at startup
var currentPlatform = PlatformDetector.DetectPlatformSync();
// Social authentication with platform detection
builder.Services.AddSocialLoginUI(x =>
{
x.ApiUrl = "https://api.fantasoccer.com";
x.Platform = new PlatformConfig
{
Type = currentPlatform,
RedirectUri = currentPlatform switch
{
PlatformType.iOS => "msauth://com.keyserdsoze.fantasoccer/auth",
PlatformType.Android => "fantasoccer://oauth/callback",
_ => null // Web: use NavigationManager.BaseUri
},
RedirectPath = "/account/login",
LoginMode = LoginMode.Redirect
};
// Read from configuration
x.Microsoft.ClientId = builder.Configuration["Microsoft:ClientId"];
x.Google.ClientId = builder.Configuration["Google:ClientId"];
});
// Repository with automatic authorization
builder.Services.AddRepository<User, Guid>(repositoryBuilder =>
{
repositoryBuilder.WithApiClient(apiBuilder =>
{
apiBuilder.WithHttpClient("https://api.fantasoccer.com")
.WithDefaultRetryPolicy();
});
});
builder.Services.AddDefaultAuthorizationInterceptorForApiHttpClient();
return builder.Build();
}
}
msauth://com.yourapp.bundle/authyourapp://oauth/callbackcom.yourapp.bundlecom.yourappiOS Bundle ID Format:
msauth://com.yourcompany.yourapp/auth
Android Package Name Format:
yourapp://oauth/callback
Important: Deep links must match exactly between:
PlatformConfig.RedirectUri in your codeWhen a user clicks a social login button, the library determines the OAuth redirect URI using this priority order:
// SocialLoginManager.cs - GetFullRedirectUri() method
// Priority 1: Explicit platform.RedirectUri (highest priority)
if (!string.IsNullOrEmpty(_settings.Platform?.RedirectUri))
{
redirectUri = _settings.Platform.RedirectUri;
}
// Priority 2: NavigationManager.BaseUri (Blazor default)
else
{
redirectUri = _navigationManager.BaseUri.TrimEnd('/');
}
// Append path
var path = _settings.Platform?.RedirectPath ?? "/account/login";
return $"{redirectUri}{path}";
MauiProgram.cs):builder.Services.AddSocialLoginUI(x =>
{
x.ApiUrl = "https://api.yourdomain.com";
x.Platform = new PlatformConfig
{
Type = PlatformType.iOS,
RedirectUri = "msauth://com.yourapp.bundle/auth", // Mobile deep link
RedirectPath = "/account/login"
};
x.Microsoft.ClientId = "your-client-id";
});
User Clicks MicrosoftButton.razor:
Manager.GetFullRedirectUri() → Returns: msauth://com.yourapp.bundle/auth/account/loginUri.EscapeDataString(redirectUri)NavigationManager.NavigateTo(oauthUrl)OAuth Provider Redirects:
msauth://com.yourapp.bundle/auth?code=ABC123&state=XYZ/account/login?code=ABC123&state=XYZToken Exchange:
SocialAuthenticationRouter detects callbackPOST /api/Authentication/Social/Token?provider=Microsoft&code=ABC123&redirectPath=/account/login// Compile-time detection (recommended for MAUI)
public static PlatformType DetectPlatformSync()
{
#if IOS
return PlatformType.iOS;
#elif ANDROID
return PlatformType.Android;
#else
return PlatformType.Web;
#endif
}
✅ DO:
PlatformType.Auto for automatic detectionPlatform.RedirectUri explicitly for MAUI#if IOS / #elif ANDROID compiler directives❌ DON'T:
https://) for mobile appsRedirectPath across platforms| Feature | Blazor Server/WASM | Blazor Hybrid (MAUI) |
|---|---|---|
| Platform | Web browsers | iOS + Android |
| Redirect URI | https://yourdomain.com | Deep link (msauth://, myapp://) |
| Login Flow | OAuth redirect | OAuth with deep link callback |
| Token Storage | localStorage (JSInterop) | Secure Storage (MAUI) |
| PKCE | ✅ Required | ✅ Required |
| Popup Mode | ⏳ Coming soon | ❌ Not applicable |
public class CustomSocialUser : DefaultSocialUser
{
public string DisplayName { get; set; }
public Guid UserId { get; set; }
public string Avatar { get; set; }
public List<string> Roles { get; set; }
}
Access in components:
@code {
[CascadingParameter(Name = "SocialUser")]
public SocialUserWrapper<CustomSocialUser>? SocialUser { get; set; }
private void ShowUserInfo()
{
var displayName = SocialUser?.User?.DisplayName;
var userId = SocialUser?.User?.UserId;
var roles = SocialUser?.User?.Roles;
}
}
@inject SocialLoginManager LoginManager
@inject SocialLoginLocalStorageService LocalStorage
private async Task<string?> GetAccessTokenAsync()
{
var token = await LocalStorage.GetTokenAsync();
return token?.AccessToken;
}
private async Task RefreshTokenAsync()
{
var token = await LoginManager.FetchTokenAsync();
// Token automatically refreshed if expired
}
With AddDefaultAuthorizationInterceptorForApiHttpClient(), all API calls automatically include Bearer token:
@inject IRepository<Order, Guid> OrderRepository
private async Task<List<Order>> GetOrdersAsync()
{
// Authorization header automatically added
return await OrderRepository.Query()
.Where(x => x.UserId == currentUserId)
.ToListAsync();
}
https://yourdomain.com/account/login (Web platform)https://yourdomain.com/account/loginhttps://yourdomain.com/account/login{
"SocialLogin": {
"Microsoft": {
"ClientId": "0b90db07-be9f-4b29-b673-9e8ee9265927"
},
"Google": {
"ClientId": "23769141170-lfs24avv5qrj00m4cbmrm202c0fc6gcg.apps.googleusercontent.com"
},
"Facebook": {
"ClientId": "345885718092912"
}
}
}
⚠️ Note: Only ClientId is needed on client-side. ClientSecret should only be configured on the API server.
Rystem.Authentication.Social - Backend OAuth token validation with PKCErystem.authentication.social.react - React components with TypeScriptRystem.Authentication.Social.Abstractions - Shared models