How-To: Get Started with Oidc Authentication
UnoFeatures:
AuthenticationOidc(add to<UnoFeatures>in your.csproj)
OidcAuthenticationProvider is a specific implementation of IAuthenticationProvider that allows your users to sign in using their identities from a participating identity provider. It provides seamless integration with any OpenID Connect backend, such as Duende.IdentityServer. By acting as an adapter, it integrates OpenID Connect authentication into the Uno.Extensions ecosystem, allowing you to leverage a unified approach across platforms.
Under the hood, OidcAuthenticationProvider relies on Duende.IdentityModel.OidcClient, for all non-windows targets, as a widely-used .NET library that handles the core OpenID Connect and OAuth 2.0 protocols. This library manages the complex operations like token handling and user authentication.
Note
It may be useful to familiarize yourself with the OpenID Connect protocol before proceeding. You can also explore the Uno Tutorial for Oidc Authentication to learn more about the particularities for different platforms.
Step-by-step
Important
This guide assumes you used the template wizard or dotnet new unoapp to create your solution. If not, it is recommended that you follow the Creating an application with Uno.Extensions documentation to create an application from the template.
1. Prepare for OIDC authentication
For this type of authentication, the application must already be registered with the desired identity provider.
A client id (and client secret) will be provided to you.
Add
AuthenticationOidcto the<UnoFeatures>property in the Class Library (.csproj) file.<UnoFeatures> Material; + AuthenticationOidc; Toolkit; MVUX; </UnoFeatures>
2. Set up OIDC authentication
Use the
UseAuthentication()extension method to configure theIHostBuilderto use an authentication provider. In our case, we will be using theOidcAuthenticationProvider.private IHost Host { get; set; } protected override void OnLaunched(LaunchActivatedEventArgs args) { var builder = this.CreateBuilder(args) .Configure(host => { host .UseAuthentication(builder => { // Add the authentication provider here }); }); ... }Add the
OidcAuthenticationProviderusing theAddOidc()extension method which configures theIAuthenticationBuilderto use it.private IHost Host { get; set; } protected override void OnLaunched(LaunchActivatedEventArgs args) { var builder = this.CreateBuilder(args) .Configure(host => { host .UseAuthentication(builder => { builder.AddOidc(); }); }); ... }The
IAuthenticationBuilderis responsible for managing the lifecycle of the associated provider that was built.Because it is configured to use OpenID Connect, the user will eventually be prompted to sign in to their identity provider account when they use your application.
OidcAuthenticationProviderwill then store the user's access token in credential storage. The token will be automatically refreshed when it expires.
3. Configure the OidcAuthenticationProvider
While the
AddOidc()extension method will configure theIAuthenticationBuilderto use theOidcAuthenticationProvider, it will not configure the provider itself. TheOidcAuthenticationProvidercan be configured using a configuration section.We will use the default section name
Oidcin this example:{ "Oidc": { "Authority": "https://demo.duendesoftware.com/", "ClientId": "interactive.confidential", "ClientSecret": "secret", "Scope": "openid profile email api offline_access", "RedirectUri": "oidc-auth://callback", } }On Skia Desktop the redirect URI must be a loopback HTTP address instead of a custom scheme; see Skia Desktop below.
Common scopes include
openid,profile,email, andoffline_access. Theopenidscope is required for OpenID Connect authentication. Theprofileandemailscopes are used to request additional user information, and theoffline_accessscope is used to request a refresh token. Your identity provider may provide additional scopes, such asapi, to request access to specific APIs.Authority: The URL of the identity provider.ClientIdandClientSecret: The client ID and client secret that were provided to you.Scope: The scope of the access token.RedirectUri: The URL that the identity provider will redirect to after the user has authenticated.It is also possible to populate this setting automatically from the WebAuthenticationBroker using the
.AutoRedirectUriFromWebAuthenticationBroker()extension method (builder.AddOidc(oidc => oidc.AutoRedirectUriFromWebAuthenticationBroker())), which will set the redirect URI to the value returned by theWebAuthenticationBroker.GetCurrentApplicationCallbackUri()method, which should discover the correct redirect URI for the application/platform. More information can be found in the Web Authentication Broker documentation.When used, this setting will override the value set in the configuration file for both the redirect URI and the post-logout redirect URI. This setting is ON by default on WebAssembly but opt-in on other platforms.
Advanced settings: some advanced settings may need to access directly the
OidcClientOptionsfromIdentityModel.OidcClientused by this extension. This can be done by using the.ConfigureOidcClientOptions()extension method.builder.AddOidc(oidc => oidc .ConfigureOidcClientOptions(options => { // Example of advanced settings for the OidcClientOptions options.DisablePushedAuthorization = true; // Disable the PAR endpoint options.Policy.RequireIdentityTokenOnRefreshTokenResponse = true; // Require an identity token on refresh token response options.Policy.Discovery.ValidateIssuerName = false; // Disable issuer name validation }));
Skia Desktop (Windows, macOS, Linux)
Uno Platform has no built-in WebAuthenticationBroker on the Skia Desktop target, so AddOidc() registers one: DesktopWebAuthenticationBrokerProvider, which opens the sign-in page in the system browser and receives the redirect on a loopback HTTP listener. It is registered through Uno's ApiExtensibility registry, where the first registration wins, so an app that supplies its own IWebAuthenticationBrokerProvider must register it before calling AddOidc().
- The
RedirectUri(andPostLogoutRedirectUri) must be a loopback HTTP address, for examplehttp://localhost:5001/authentication-callback, and that exact value must be registered with the identity provider. A custom scheme is not routed back to a desktop app. WebAuthenticationBroker.GetCurrentApplicationCallbackUri()returnshttp://localhost:{port}/authentication-callbackon this target, with a port the broker picks once per process; setWinRTFeatureConfiguration.WebAuthenticationBroker.DefaultReturnUrito pin it. Do not call it beforeAddOidc()has run: the broker resolves its provider on first use, and anything that touchesWebAuthenticationBrokerearlier pins Uno's built-in provider, which throws on this target. A sign-in that fails with an error namingDesktopWebAuthenticationBrokerProvider.TryRegister()(the built-in provider'sNotImplementedException) is this case: move the earlier call afterUseAuthentication. A provider the app registers afterAddOidc()is ignored for the same reason: the registry keeps the first registration.- Whether the loopback broker took the registration, or an earlier one kept it, is logged through the host's logger once the host is built (
Uno.Extensions.Authenticationcategory, Debug and Information). That line is written by anIServiceInitialize, which only the Uno host (this.CreateBuilder(args)orUnoHost.CreateDefaultBuilder()) runs, so a host built some other way never writes it. Independently of the host, the broker's first sign-in flow logs at Information on Uno's ambient logger that the loopback broker is handling it; if the first sign-in logs no such line, an app-supplied provider handled it. Both lines sit under theUno.category, so they only show onceCoreLogLevelisInformationor lower. The broker's own diagnostics (a port that could not be bound, a timed-out flow, a browser that would not open) go to Uno's ambient logger, whichUseLogging()only connects withenableUnoLogging: true; its per-flow lines are at Debug, below theUnofilter the usual log levels apply. - Apps that call
WebAuthenticationBrokerdirectly can register the broker themselves withDesktopWebAuthenticationBrokerProvider.TryRegister()during startup. - Sign-out sends the cached ID token as
id_token_hint, which the identity provider needs before it will redirect back toPostLogoutRedirectUri. Some providers redirect at once; others, such as the Duende demo server, show a signed-out page with a link back to the app, and the sign-out completes when the user follows it. - If no redirect arrives within
WinRTFeatureConfiguration.WebAuthenticationBroker.AuthenticationTimeout, the flow is reported as cancelled withResponseErrorDetailset toDesktopWebAuthenticationBrokerProvider.TimeoutErrorDetail(408). - To open the page some other way (a specific browser, a private window), derive from
DesktopWebAuthenticationBrokerProvider, overrideLaunchBrowserAsync, and register your type withApiExtensibility.RegisterbeforeAddOidc(). The type only exists in the non-WinAppSDK builds of the package, so guard the reference with#if !WINDOWSin code shared with a WinAppSDK head.
4. Use the provider in your application
Update the
MainPageto include a button that will allow the user to sign in with your desired service.<Page x:Class="UnoOidcAuthentication.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:UnoOidcAuthentication" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Button x:Name="SignInButton" Content="Sign In" Command="{x:Bind ViewModel.Authenticate}" /> </Grid> </Page>Update the
MainViewModelto include a command that will allow the user to sign in with your desired service.public class MainViewModel : ObservableObject { private readonly IAuthenticationService _authenticationService; public MainViewModel(IAuthenticationService authenticationService) { _authenticationService = authenticationService; } public ICommand Authenticate => new AsyncRelayCommand(AuthenticateImpl); private async Task AuthenticateImpl() { await _authenticationService.LoginAsync(/* ... */); } }Finally, we can pass the login credentials to the
LoginAsync()method and authenticate with the identity provider. The user will be prompted to sign in to their account when they tap the button in the application.OidcAuthenticationProviderwill then store the user's access token in credential storage. The token will be automatically refreshed when it expires.
Advanced Customizations
You can use your own implementation of
IBrowser(an interface from theIdentityModel.OidcClientlibrary) to customize the browser behavior. This should be done by creating a class that implements the interface and register it using the.ConfigureServices()in theApp.xaml.csfile..ConfigureServices((context, services) => { services.AddTransient<IBrowser, CustomBrowser>(); })