Trying to get AspNetZero 6.4 MVC Core/jQuery functioning inside a docker container. The docker container runs and the application loads in the browser, but it looks like some of the javascript isn't loading properly. On the login screen, I get two javasript errors:
It's ok when I run it from the Visual Studio directly. I don't get those errors. I've tried running yarn and npm run create-bundles many times but I still get the same result in docker. I haven't modified the build-mvc.ps1 script at all.
Any thoughts?
We were having trouble with getting aspnetzero MVC/jQuery to work with redis behind a load balancer. We couldn't get it to work unless we used sticky sessions. We think we finally figured it out. We had to add the following code to persist the keys to redis in the startup.cs class:
if (_appConfiguration["Abp:RedisCache:PersistKeysToRedis"] != null && bool.Parse(_appConfiguration["Abp:RedisCache:PersistKeysToRedis"]))
{
var redis = ConnectionMultiplexer.Connect(_appConfiguration["Abp:RedisCache:ConnectionString"]);
services.AddDataProtection()
.PersistKeysToRedis(redis, "DataProtection-Keys");
}
else
{
services.AddDataProtection().DisableAutomaticKeyGeneration();
}
Is that the correct solution? If so, should I add it as an issue in github?
We are creating an organizational unit structure that has many individual items (~20,000). As of the moment, the tree loads but it is very slow. Any thoughts on how to make the tree use lazy loading (load the child OUs on click of the parent)? Would that be a huge effort?
Thanks, Craig
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.
Thanks @ismcagdas,
Can I send you my source code to take a look at? I've set-up the cors stuff. It's literally driving me crazy. I have a test project that I am trying to use as a proof of concept. What is the best way to do that?