I'm setting up a .Net 8 asp mvc app with and external Oauth2 SSO Login. So I using a custom AuthenticationHandler with these HandleAuthenticateAsync() & HandleChallengeAsync()
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
try
{
AuthenticationProperties properties = null;
string code = "";
string state = "";
var values = Request.Query["code"];
if (values.Count == 1)
code = values[0];
values = Request.Query["state"];
if (values.Count == 1)
state = values[0];
if (!string.IsNullOrEmpty(state))
properties = Options.StateDataFormat.Unprotect(state);
if (properties == default(AuthenticationProperties))
{
if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path)
return AuthenticateResult.Fail("Invalid state parameter");
}
/*
Here I ask for an accessToken and with it I request the user information from the auth server
And do logic about the roles and claims
await _signInManager.SignInAsync(applicationUser, isPersistent: true, "myScheme");
//Success! Add details here that identifies the user
var newClaims = await _userManager.GetClaimsAsync(applicationUser);
newClaims.Add(new Claim("Name", user.given_name));
var userRoles = await _userManager.GetRolesAsync(applicationUser);
foreach (var role in userRoles)
{
newClaims.Add(new Claim(ClaimTypes.Role, role));
}
var Identity = new ClaimsIdentity(newClaims, "id_code", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
var claimsPrincipal = new ClaimsPrincipal(Identity);
await Options.Provider.Authenticated(Context);
return AuthenticateResult.Success(new AuthenticationTicket(claimsPrincipal, properties, "myScheme"));
}
catch (Exception ex)
{
return AuthenticateResult.Fail("Unauthorized");
}
}
protected override async Task<Task> HandleChallengeAsync(AuthenticationProperties properties)
{
try
{
var baseUri =
Request.Scheme +
Uri.SchemeDelimiter +
Request.Host +
Request.PathBase;
var currentUri =
baseUri +
Request.Path +
Request.QueryString;
var redirectUri =
baseUri +
Options.CallbackPath;
//var properties = challenge.Properties;
if (string.IsNullOrEmpty(properties.RedirectUri))
{
properties.RedirectUri = currentUri;
}
// Generate a correlation ID and store it in AuthenticationProperties
var correlationId = Guid.NewGuid().ToString();
properties.Items[".AspNet.Correlation.id_code"] = correlationId;
// comma separated
var scope = string.Join(",", Options.Scope);
var state = Options.StateDataFormat.Protect(properties);
var optionsScope = Options.GetScope();
var scopeToUri = Uri.EscapeDataString(optionsScope);
var authorizationEndpoint =
Options.Endpoints.AuthorizationEndpoint +
"&client_id=" + Uri.EscapeDataString(Options.ClientId) +
"&redirect_uri=" + Uri.EscapeDataString(redirectUri) +
"&scope=" + scopeToUri +
"&response_type=" + "code" +
"&state=" + Uri.EscapeDataString(state);
Response.Redirect(authorizationEndpoint);
return Task.CompletedTask;
}
catch (Exception e)
{
_logger.LogError(e.Message);
throw new Exception(e.Message);
}
}
public static class myAuthenticationExtensions
{
public static AuthenticationBuilder UseMyAuthentication(
this AuthenticationBuilder builder,
string authenticationScheme,
Action<myAuthenticationOptions> configureOptions)
{
if (configureOptions == null)
throw new ArgumentNullException(nameof(configureOptions));
return builder.AddScheme<myAuthenticationOptions, myAuthenticationHandler>(
authenticationScheme, configureOptions);
}
}
and here my are my ConfigureServices() and Configure()
public void ConfigureServices(IServiceCollection services)
{
services.adddbcontext<applicationdbcontext>(options =>
options.usesqlserver(configuration.getconnectionstring("myConnection")));
services.addidentity<applicationuser, applicationrole>()
.addentityframeworkstores<applicationdbcontext>()
.adddefaulttokenproviders()
.addsigninmanager<signinmanager<applicationuser>>()
.addusermanager<usermanager<applicationuser>>()
.addrolemanager<rolemanager<applicationrole>>();
//services.addscoped<rolemanager<applicationrole>>();
//services.addscoped<usermanager<applicationuser>>(); // assuming applicationusermanager is a non-generic type
//services.addscoped<signinmanager<applicationuser>>(); // assuming applicationsigninmanager is a non-generic type
// add authentication services
services.addauthentication(options =>
{
options.defaultauthenticatescheme = "myScheme";
options.defaultchallengescheme = "myScheme"; // your custom authentication scheme
})
.UseMyAuthentication("myScheme", options =>
{
// set options properties using values from appsettings.json
options.clientid = configuration.getsection("appsettings")["authserver:clientid"];
options.clientsecret = configuration.getsection("appsettings")["authserver:clientsecret"];
options.callbackpath = new pathstring("/oauth/callback");
var dataprotectionprovider = services.buildserviceprovider().getdataprotectionprovider();
var dataprotector = dataprotectionprovider.createprotector(typeof(uscauthenticationextensions).fullname);
options.statedataformat = new propertiesdataformat(dataprotector);
options.provider = new myAuthenticationprovider();
options.endpoints.authorizationendpoint = configuration.getsection("appsettings")["authserver:authorizationendpoint"];
options.endpoints.tokenendpoint = configuration.getsection("appsettings")["authserver:tokenendpoint"];
options.endpoints.userinfoendpoint = configuration.getsection("appsettings")["authserver:userinfoendpoint"];
})
.addcookie();
// add services to the container.
services.addrazorpages();
services.addhttpforwarder();
services.addhttpclient();
services.addhttpcontextaccessor();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// Use the routes configured in RouteConfig
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Well, when I run the app by first time, it enters the HandleAuthenticateAsync () and retrieve a Fail after to check that properties == null
Then enter the HandleChallengeAsync () which shows me up the provider login, after to successfully enter my credentials, the app starts to load the _loginPartial
and debugging the razor pages I see that the
IsAuthenticated = true
and the users, roles and claims.
But the problems are when it comes to a second request, which is while loading the rest of the page, it enters HandleAuthenticateAsync () with
signInManager.Context.IsAuthenticated = false
I'm expecting to have the user already logged in, the signInManager the App. And worst, the HandleChallengeAsync () is not called anymore
I don't know what I'm loosing in the auth scheme.
Can anybody help with this?
I'm expecting to login onece and the navigate across the application with the user credentials.