I'm using I have authentication with AzureAD set up using OpenID Connect with an ASP.NET Core 2.2 MVC app. I want to be able to log info about the user right after a successful sign-in into the application.
In the code below, I use options.Events.OnTicketReceived event to add role claims from an external database to the Identity after they have successfully authenticated with AzureAD. However, my research tells me that the OnTicketReceived event is called right before the user is signed-in, that is, right before the local cookie is created and the user is authenticated with the app (Source: post1, post2). This would mean that the user hasn't been authenticated with the app yet, and I don't know if after the OnTicketReceived event the user is guaranteed to be authenticated.
All of this makes me wonder is it correct to log my "user signed in" message on the OnTicketReceived event or is there another way of doing it? I am also unsure if it is in Startup.cs that I should be logging this info.
Startup.cs > ConfigureServices method
public void ConfigureServices(IServiceCollection Services)
{
//https://weblog.west-wind.com/posts/2017/dec/12/easy-configuration-binding-in-aspnet-core-revisited
IAppSettings config = new AppSettings();
Configuration.Bind("AppSettings", config);
Services.AddMvc();
Services.AddSingleton(config);
//bunch of service dependencies here...
Services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
Services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options =>
{
options.Instance = config.AzureAD.Instance;
options.Domain = config.AzureAD.Domain;
options.TenantId = config.AzureAD.TenantId; //TenantID is set to "organizations"
options.ClientId = config.AzureAD.ClientId; //the ID of the registered app in AzureAD
options.CallbackPath = config.AzureAD.CallbackPath;
});
Services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.UseTokenLifetime = false;
options.Authority = options.Authority + "/v2.0/"; //Microsoft identity platform
options.TokenValidationParameters.ValidateIssuer = true;
options.TokenValidationParameters.ValidIssuers = config.AzureAD.Organizations; //List<string> of allowed organizations
// https://stackoverflow.com/questions/49469979/azure-ad-b2c-user-identity-name-is-null-but-user-identity-m-instance-claims9
// https://stackoverflow.com/questions/54444747/user-identity-name-is-null-after-federated-azure-ad-login-with-aspnetcore-2-2
options.TokenValidationParameters.NameClaimType = "http://schemas.microsoft.com/identity/claims/objectidentifier";
//Code below is to add claims during login (we add roles from db): https://stackoverflow.com/questions/51965665/adding-custom-claims-to-claimsprincipal-when-using-addazureadb2c-in-mvc-core-app
//some discussion about this here: https://stackoverflow.com/questions/59564952/net-core-add-claim-after-azuerad-authentication
//and here: https://stackoverflow.com/questions/52727146/net-core-2-openid-connect-authentication-and-multiple-identities
options.Events.OnTicketReceived = context =>
{
string userEmail = context.Principal.FindFirstValue(ClaimTypes.Email);
//I get an instance of UserAccess service so I can check termination status and get the roles by email.
IUserAccess userAccess = context.HttpContext.RequestServices.GetService<IUserAccess>();
bool terminated = userAccess.GetUserTerminationStatusByUserEmail(userEmail, config.ConnectionString);
if (terminated == false)
{
ClaimsIdentity claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
List<string> roles = userAccess.GetUserRolesByUserEmail(userEmail, config.ConnectionString);
foreach (var role in roles)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role));
}
}
return Task.CompletedTask;
};
});
Services.Configure<CookieAuthenticationOptions>(AzureADDefaults.CookieScheme, options =>
{
options.AccessDeniedPath = "/UserAccess/NotAuthorized";
options.LogoutPath = "/UserAccess/SignOut";
options.ExpireTimeSpan = TimeSpan.FromMinutes(config.AzureAD.TimeoutInMinutes);
options.SlidingExpiration = true;
});
}
To sign-in and sign-out, I use the existing functions from Microsoft.AspNetCore.Authentication.AzureAD.UI NuGet package, and call them in my views like this:
<!-- to sign out -->
<a asp-area="AzureAD" asp-controller="Account" asp-action="SignOut">Sign out</a>
<!-- to sign in -->
<a asp-area="AzureAD" asp-controller="Account" asp-action="SignIn">Sign in with Microsoft</a>
