Skip to content

Instantly share code, notes, and snippets.

@dgt0011
Created May 22, 2024 10:41
Show Gist options
  • Save dgt0011/b9cf97cd12a8ccd7aa1609fdd7484fe5 to your computer and use it in GitHub Desktop.
Save dgt0011/b9cf97cd12a8ccd7aa1609fdd7484fe5 to your computer and use it in GitHub Desktop.
Modified version of the BlazorWebAppOidc.Program.cs file to support using AWS Cognito as the OIDC provider for a ASP.Net Core 8 Blazor sample project from https://learn.microsoft.com/en-us/aspnet/core/blazor/security/blazor-web-app-with-oidc?view=aspnetcore-8.0&pivots=without-bff-pattern
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using BlazorWebAppOidc;
using BlazorWebAppOidc.Client.Weather;
using BlazorWebAppOidc.Components;
using BlazorWebAppOidc.Weather;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.DependencyInjection;
const string AWS_OIDC_SCHEME = "CognitoOidc";
//reference: https://learn.microsoft.com/en-us/aspnet/core/blazor/security/blazor-web-app-with-oidc?view=aspnetcore-8.0&pivots=without-bff-pattern
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthentication(AWS_OIDC_SCHEME)
.AddOpenIdConnect(AWS_OIDC_SCHEME, oidcOptions =>
{
// For the following OIDC settings, any line that's commented out
// represents a DEFAULT setting. If you adopt the default, you can
// remove the line if you wish.
// ........................................................................
// The OIDC handler must use a sign-in scheme capable of persisting
// user credentials across requests.
oidcOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
// ........................................................................
// ........................................................................
// The "openid" and "profile" scopes are required for the OIDC handler
// and included by default. You should enable these scopes here if scopes
// are provided by "Authentication:Schemes:MicrosoftOidc:Scope"
// configuration because configuration may overwrite the scopes collection.
//oidcOptions.Scope.Add(OpenIdConnectScope.OpenIdProfile);
oidcOptions.Scope.Clear();
// required OIDC scopes
oidcOptions.Scope.Add("openid");
oidcOptions.Scope.Add("profile");
// default Cognito Scope
oidcOptions.Scope.Add("email");
// TODO: replace {User pool ID} below with the actual User pool ID from your User Pool
oidcOptions.MetadataAddress = "https://cognito-idp.ap-southeast-2.amazonaws.com/{User pool ID}/.well-known/openid-configuration";
oidcOptions.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProviderForSignOut = context =>
{
// TODO: replace {Cognito domain} with the actual domain value from the User Pool - e.g. https://my-test-app.auth.ap-southeast-2.amazoncognito.com if you use the attached Cloudformation template
var uri = new Uri("{Cognito domain}/logout", UriKind.Absolute);
// set the logout page that will be redirected to when logging out.
// if this is changed to be a different page, be sure to add or change the LogoutUrls in the cloudformation template
// or edit the Allowed sign-out URLs in the Hosted UI section of the App client
var logoutUrl = $"{context.Request.Scheme}://{context.Request.Host}/";
context.ProtocolMessage.IssuerAddress = uri.AbsoluteUri;
context.ProtocolMessage.ResponseType = "code";
// TODO: Replace {ClientId} with the Client Id from the App Client.
context.ProtocolMessage.SetParameter("client_id", "{ClientId}");
context.ProtocolMessage.SetParameter("logout_uri", logoutUrl);
context.ProtocolMessage.SetParameter("redirect_uri", logoutUrl);
return Task.CompletedTask;
}
};
// ........................................................................
// ........................................................................
// The following paths must match the redirect and post logout redirect
// paths configured when registering the application with the OIDC provider.
// For Microsoft Entra ID, this is accomplished through the "Authentication"
// blade of the application's registration in the Azure portal. Both the
// signin and signout paths must be registered as Redirect URIs. The default
// values are "/signin-oidc" and "/signout-callback-oidc".
// Microsoft Identity currently only redirects back to the
// SignedOutCallbackPath if authority is
// https://login.microsoftonline.com/{TENANT ID}/v2.0/ as it is above.
// You can use the "common" authority instead, and logout redirects back to
// the Blazor app. For more information, see
// https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/5783
oidcOptions.CallbackPath = new PathString("/signin-oidc");
oidcOptions.SignedOutCallbackPath = new PathString("/signout-callback-oidc");
// ........................................................................
// ........................................................................
// The RemoteSignOutPath is the "Front-channel logout URL" for remote single
// sign-out. The default value is "/signout-oidc".
oidcOptions.RemoteSignOutPath = new PathString("/signout-oidc");
// ........................................................................
// ........................................................................
// The following example Authority is configured for Microsoft Entra ID
// and a single-tenant application registration. Set the {TENANT ID}
// placeholder to the Tenant ID. The "common" Authority
// https://login.microsoftonline.com/common/v2.0/ should be used
// for multi-tenant apps. You can also use the "common" Authority for
// single-tenant apps, but it requires a custom IssuerValidator as shown
// in the comments below.
// TODO: Replace {Cognito domain} obviously with the actual domain value from the User Pool - e.g. https://my-test-app.auth.ap-southeast-2.amazoncognito.com if you use the attached Cloudformation template
oidcOptions.Authority = "{Cognito domain}/oauth2/authorize";
// ........................................................................
// ........................................................................
// Set the Client ID for the app. Set the {CLIENT ID} placeholder to
// the Client ID.
oidcOptions.ClientId = "5a7ovdoip4astq0823rv1uq7ko"; // Cognito App Client Client ID
// ........................................................................
// ........................................................................
// ClientSecret shouldn't be compiled into the application assembly or
// checked into source control. Adopt User Secrets, Azure KeyVault,
// or an environment variable to supply the value. Authentication scheme
// configuration is automatically read from
// "Authentication:Schemes:{SchemeName}:{PropertyName}", so ClientSecret is
// for OIDC configuration is automatically read from
// "Authentication:Schemes:MicrosoftOidc:ClientSecret" configuration.
oidcOptions.ClientSecret = "uccma5rbi25pm1nuq2lsc8icahuvct23g747jafmcrke6mv3a35"; // Cognito App Client Secret
// ........................................................................
// ........................................................................
// Setting ResponseType to "code" configures the OIDC handler to use
// authorization code flow. Implicit grants and hybrid flows are unnecessary
// in this mode. In a Microsoft Entra ID app registration, you don't need to
// select either box for the authorization endpoint to return access tokens
// or ID tokens. The OIDC handler automatically requests the appropriate
// tokens using the code returned from the authorization endpoint.
oidcOptions.ResponseType = OpenIdConnectResponseType.Code;
// ........................................................................
// ........................................................................
// Many OIDC servers use "name" and "role" rather than the SOAP/WS-Fed
// defaults in ClaimTypes. If you don't use ClaimTypes, mapping inbound
// claims to ASP.NET Core's ClaimTypes isn't necessary.
oidcOptions.MapInboundClaims = false;
oidcOptions.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Name;
oidcOptions.TokenValidationParameters.RoleClaimType = "role";
// ........................................................................
// ........................................................................
// Many OIDC providers work with the default issuer validator, but the
// configuration must account for the issuer parameterized with "{TENANT ID}"
// returned by the "common" endpoint's /.well-known/openid-configuration
// For more information, see
// https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1731
//var microsoftIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(oidcOptions.Authority);
//oidcOptions.TokenValidationParameters.IssuerValidator = microsoftIssuerValidator.Validate;
// ........................................................................
// ........................................................................
// OIDC connect options set later via ConfigureCookieOidcRefresh
//
// (1) The "offline_access" scope is required for the refresh token.
//
// (2) SaveTokens is set to true, which saves the access and refresh tokens
// in the cookie, so the app can authenticate requests for weather data and
// use the refresh token to obtain a new access token on access token
// expiration.
// ........................................................................
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
// ConfigureCookieOidcRefresh attaches a cookie OnValidatePrincipal callback to get
// a new access token when the current one expires, and reissue a cookie with the
// new access token saved inside. If the refresh fails, the user will be signed
// out. OIDC connect options are set for saving tokens and the offline access
// scope.
//builder.Services.ConfigureCookieOidcRefresh(CookieAuthenticationDefaults.AuthenticationScheme, AWS_OIDC_SCHEME);
builder.Services.AddAuthorization();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
builder.Services.AddScoped<AuthenticationStateProvider, PersistingAuthenticationStateProvider>();
builder.Services.AddScoped<IWeatherForecaster, ServerWeatherForecaster>();
builder.Services.AddHttpContextAccessor();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weather-forecast", ([FromServices] IWeatherForecaster WeatherForecaster) =>
{
return WeatherForecaster.GetWeatherForecastAsync();
}).RequireAuthorization();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(BlazorWebAppOidc.Client._Imports).Assembly);
app.MapGroup("/authentication").MapLoginAndLogout();
app.Run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment