I've been struggling with this one for a few days now, so this is my last hope!
I'm using IdentityServer4 with .NET Core 6 (Id3 worked for this originally with .NET Core 3).
// Create user acount here (which works, they're registered)
SignInResult result = await _signInManager.PasswordSignInAsync(appUser, viewModel.Password, true, lockoutOnFailure: false);
if (result.Succeeded)
{
await _events.RaiseAsync(new UserLoginSuccessEvent(appUser.UserName, appUser.Id, appUser.UserName, clientId: string.Empty));
var result1 = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
var additionalLocalClaims = new List<Claim>();
var localSignInProps = new AuthenticationProperties();
ProcessLoginCallback(result1, additionalLocalClaims, localSignInProps);
var isuser = new IdentityServerUser(appUser.Id)
{
DisplayName = createdUser.Username,
AdditionalClaims = additionalLocalClaims,
AuthenticationTime = DateTime.Now
};
// Tried this too
// ClaimsPrincipal principle = isuser.CreatePrincipal();
// await HttpContext.SignInAsync(principle, localSignInProps);
await HttpContext.SignInAsync(isuser, localSignInProps);
// Redirecting them to the main app here - when they get here they aren't authenticated.
return Redirect(this._redirectURL + "/LMS/Course");
}
private void ProcessLoginCallback(AuthenticateResult externalResult, List<Claim> localClaims, AuthenticationProperties localSignInProps)
{
if (AccountOptions.AllowRememberLogin)
{
localSignInProps = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
};
// if the external system sent a session id claim, copy it over
// so we can use it for single sign-out
var sid = externalResult.Principal?.Claims?.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId);
if (sid != null)
{
localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
}
// if the external provider issued an id_token, we'll keep it for signout
var idToken = externalResult?.Properties?.GetTokenValue("id_token");
if (idToken != null)
{
localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = idToken } });
}
}
This all goes through okay, but then they get sent through to the actual application... for them to not be authenticated or to have any claims stored.
This is the exact same code as the Login method, which works. In fact, if I then go to the Login page and authenticate using the same login account as that has just been created, they are authenticated fine with the correct claims. Also, the Login + Registration (the method shown above) methods are in the same controller, on the same auth server.
I can't seem to work out why these two processes would work completely differently using the same authentication code, in the same controller?
After the user as successfully authenticated in IdentityServer, it needs to redirect back to the OpenIDConnect authentication handler in the client, so it can create the user session and get the final tokens.
So I suspect this one is the problem:
// Redirecting them to the main app here - when they get here they aren't authenticated.
return Redirect(this._redirectURL + "/LMS/Course");
Typically, you should redirect back to the /signin-oidc URL with the authentication code.
See the code here that is used in the AccountController https://github.com/DuendeSoftware/demo.duendesoftware.com/blob/8eb807e399cfd92df85d4c9d0ed1a20add35dc9c/src/Pages/Account/Login/Index.cshtml.cs#L135