Interesting. I still get the same result. I am building the site on a Mac, could that be the problem?
Just created a dropbox folder and invited you to share.
Sure, where should I send them?
This is what I got.
Not sure if it will help, but take a look at my post at:
https://support.aspnetzero.com/QA/Questions/5763
I was having the same issue with GetExternalLoginInfoAsync returning null. Specifically, take a look at the OnTicketReceived event handler I added in AuthConfigurer.cs. What I found was that the GetExternalLoginInfo method was looking for a NameIdentifier claim that is not there for the different openIdConnect vendors I've tried.
Hope it helps.
My project is .NET Core/jQuery. Sorry...
I finally figured this out. Just to be clear, I am replacing the default AspNetZero login experience with the Okta sign-in widget. (See screenshot). In addition to making changes to the login screen itself, I had to make the changes to the AuthConfigurer.cs and the AccountController.cs
The change to the AccountController was basic. Just made a new method to handle the postback from the Okta Widget:
[HttpPost]
public ActionResult ExternalLoginOkta(string sessionToken = "")
{
string provider = "OpenIdConnect";
var redirectUrl = Url.Action(
"ExternalLoginCallback",
"Account",
new
{
ReturnUrl = "/App",
authSchema = provider,
ss = ""
});
var properties = new AuthenticationProperties();
//Note: the sessionToken is created by the Okta Widget and passed into this method.
properties.Items.Add("sessionToken", sessionToken);
properties.RedirectUri = redirectUrl;
var challengeResponse = Challenge(properties, OktaDefaults.MvcAuthenticationScheme);
return challengeResponse;
}
I had to do some surgery on the AuthConfigurer.cs class. I added some event handler to OpenId to handle some apparent data either being in the incorrect place or missing from the data coming back from Okta.
public static void Configure(IServiceCollection services, IConfiguration configuration)
{
var authenticationBuilder = services.AddAuthentication();
//if (bool.Parse(configuration["Authentication:OpenId:IsEnabled"]))
//{
// authenticationBuilder.AddOpenIdConnect(options =>
// {
// options.ClientId = configuration["Authentication:OpenId:ClientId"];
// options.Authority = configuration["Authentication:OpenId:Authority"];
// options.SignedOutRedirectUri = configuration["App:WebSiteRootAddress"] + "Account/Logout";
// options.ResponseType = OpenIdConnectResponseType.IdToken;
// var clientSecret = configuration["Authentication:OpenId:ClientSecret"];
// if (!clientSecret.IsNullOrEmpty())
// {
// options.ClientSecret = clientSecret;
// }
// });
//}
if (bool.Parse(configuration["Authentication:OpenId:IsEnabled"]))
{
authenticationBuilder.AddOpenIdConnect(oidcOptions =>
{
oidcOptions.ClientId = configuration["Authentication:OpenId:ClientId"];
oidcOptions.Authority = configuration["Authentication:OpenId:Authority"];
oidcOptions.SignedOutRedirectUri = configuration["App:WebSiteRootAddress"] + "Account/Logout";
oidcOptions.ResponseType = OpenIdConnectResponseType.Code;
oidcOptions.GetClaimsFromUserInfoEndpoint = true;
oidcOptions.Scope.Add("openid");
oidcOptions.Scope.Add("profile");
oidcOptions.Scope.Add("email");
var clientSecret = configuration["Authentication:OpenId:ClientSecret"];
if (!clientSecret.IsNullOrEmpty())
{
oidcOptions.ClientSecret = clientSecret;
}
oidcOptions.Events = new OpenIdConnectEvents()
{
//TODO This was the trick to get the Okta widget to work.
OnRedirectToIdentityProvider = context =>
{
// Add Okta sessionToken to provide custom login
if (context.Properties.Items.TryGetValue("sessionToken", out var sessionToken))
{
if (!string.IsNullOrEmpty(sessionToken))
{
context.ProtocolMessage.SetParameter("sessionToken", sessionToken);
}
}
return Task.CompletedTask;
},
//TODO This is the only way i can get OpenId to work.
OnTicketReceived = context =>
{
if(!context.Properties.Items.ContainsKey("LoginProvider"))
{
//For some reason, the LoginProvider isn't being returned when using the Okta Widget
context.Properties.Items.Add("LoginProvider", "OpenIdConnect");
}
// Get the ClaimsIdentity
var identity = context.Principal.Identity as ClaimsIdentity;
if (identity != null)
{
// Add the Name ClaimType. This is required if we want User.Identity.Name to actually return something!
if (!context.Principal.HasClaim(c => c.Type == ClaimTypes.Name) &&
identity.HasClaim(c => c.Type == "preferred_username"))
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, identity.FindFirst("preferred_username").Value));
// Check if token names are stored in Properties
if (context.Properties.Items.ContainsKey(".TokenNames"))
{
// Token names a semicolon separated
string[] tokenNames = context.Properties.Items[".TokenNames"].Split(';');
// Add each token value as Claim
foreach (var tokenName in tokenNames)
{
// Tokens are stored in a Dictionary with the Key ".Token.<token name>"
string tokenValue = context.Properties.Items[$".Token.{tokenName}"];
identity.AddClaim(new Claim(tokenName, tokenValue));
}
}
}
return Task.CompletedTask;
}
};
});
}
I'm getting close.
You're saying replace GetUserInfo in the method below with the method in the ticket you mentioned. The new method takes a "token" parameter. Where does that come from?
private async Task<ExternalAuthUserInfo> GetExternalUserInfo(ExternalAuthenticateModel model)
{
var userInfo = await _externalAuthManager.GetUserInfo(model.AuthProvider, model.ProviderAccessCode);
if (userInfo.ProviderKey != model.ProviderKey)
{
throw new UserFriendlyException(L("CouldNotValidateExternalUser"));
}
return userInfo;
}
@ismcagdas - Yes, I've configured the application in Okta. I have an okta asp.net core example project that works. And, I have an AspNetZero proof of concept project that I have modified. I'm stuck at the moment.
For the purposes of this proof of concept, I've modified the aspnetzero login page to render the Okta widget and I've modified the the account controller to handle the postback in the same way the okta example works.