ASP.NET MVC client connect ids4 SSo [Authorize] 302 redirect always - single-sign-on

Client code
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
IdentityModelEventSource.ShowPII = true;
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.AccessDeniedPath = "/Authorization/AccessDenied";
})
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://jpprojectsso.inthink.top:5000"; //
options.RequireHttpsMetadata = false;
options.ClientId = "TestClient";
options.ClientSecret = "Client Secrets";
options.SaveTokens = true;
options.ResponseType = "code id_token";
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Clear();
options.Scope.Add(OidcConstants.StandardScopes.OpenId);
options.Scope.Add(OidcConstants.StandardScopes.Profile);
});
}
The controller looks like this:
[Authorize]
public IActionResult Privacy()
{
var user = User.Identity.Name;
return View();
}
ids4 connection client success. But the client always 302 redirect to sso and sso successfully redirects to client... in loop.
My sso use Jp project ids4. How can I resolve this problem?
Thanks,

I found the bug...
in void Configure function.
app.UseAuthorization() should be executed after app.UseAuthentication()
Then it works.

Related

How to Query Database From Startup.CS

I am doing user authentication in my startup.cs. I need to query my database using the OpenIDConnect claims info. This is what I have done but don't know how to get the connection to work. I tried injecting the db query constructor at the top of the startup.cs like this and then calling the query as follows:
public class Startup
{
protected IAdoSqlService _adoSqlService;
public Startup(IConfiguration configuration, IAdoSqlService adoSqlService)
{
Configuration = configuration;
_adoSqlService = adoSqlService;
}
public void ConfigureServices(IServiceCollection services)
{
// do ConfigureServices stuff
options.Events = new OpenIdConnectEvents()
{
OnTokenValidated = async ctx =>
{
// This is the ClaimsIdentity created by OpenID Connect, you can add claims to it directly
ClaimsIdentity claimsIdentity = ctx.Principal.Identities.FirstOrDefault();
string userntid = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "preferred_username").Value;
//How do I call the database to run the following query
int isUser = _adoSqlService.isUser(userntid);
if (isUser > 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "user"));
}
else
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "not authorized"));
}
}
}
//More stuff
}
}
When I run the above, it errors in program.cs before even running with the following error
System.InvalidOperationException: 'Unable to resolve service for type 'XXXX.Services.IAdoSqlService' while attempting to activate 'XXXX.Startup'.'
So how do I make the call _adoSqlService.isUser(userntid); to the database?
I am NOT using EF.
Solution
I figured this out by doing the following:
I moved most of my services to the top of the ConfigureServices section (based on something that #qudus said) before I performed my authentication.
I removed the database injection code from the top of the startup.cs.
Lastly I changed the OnTokenValidated to use the following:
ctx.HttpContext.RequestServices.GetRequiredService();
Here is the code:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
internal static IConfiguration Configuration { get; private set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var connectionSection = Configuration.GetSection("ConnectionStrings");
services.Configure<ConnectionStrings>(connectionSection);
services.AddScoped<IAdoSqlService, AdoSqlService>();
services.AddControllersWithViews();
services.AddHttpContextAccessor();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSession();
// Load the Federation configuration section from app settings
var federationConfig = Configuration.GetSection("Federation");
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromHours(2);//default is 14days
options.SlidingExpiration = true;// default
options.AccessDeniedPath = "/Error/AuthenticateError";// set a custom error access denied error page. this would need to be created/handled in your app.
})
.AddOpenIdConnect(options =>
{
//Set Options here......
//optional customizations to the auth and failure events
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = context =>
{
return Task.CompletedTask;
},
OnRemoteFailure = context =>
{
// handle an error response from Federation and redirect the user to a custom error page instead
context.Response.Redirect("/Error/401");
context.HandleResponse();
return Task.CompletedTask;
},
OnTokenValidated = async ctx =>
{
// This is the ClaimsIdentity created by OpenID Connect, you can add claims to it directly
ClaimsIdentity claimsIdentity = ctx.Principal.Identities.FirstOrDefault();
string userntid = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "preferred_username").Value;
string username = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "name").Value;
int isUser = 0;
int isAdmin = 0;
try
{
var db = ctx.HttpContext.RequestServices.GetRequiredService<IAdoSqlService>();
isUser = db.isUser(userntid);
isAdmin = db.isAdmin(userntid);
}
catch (Exception ex)
{
string error = ex.Message;
}
AppHttpContext.Current.Session.SetString("IsUser", "false");
if (isUser > 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "user"));
AppHttpContext.Current.Session.SetString("IsUser", "true");
}
AppHttpContext.Current.Session.SetString("IsUserAdmin", "false");
if (isAdmin > 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
AppHttpContext.Current.Session.SetString("IsUserAdmin", "true");
}
if (isUser == 0 && isAdmin == 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "not authorized"));
}
}
};
});
Solution
I figured this out by doing the following:
I moved most of my services to the top of the ConfigureServices section (based on something that #qudus said) before I performed my authentication.
I removed the database injection code from the top of the startup.cs.
Lastly I changed the OnTokenValidated to use the following:
ctx.HttpContext.RequestServices.GetRequiredService();
Here is the code:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
internal static IConfiguration Configuration { get; private set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var connectionSection = Configuration.GetSection("ConnectionStrings");
services.Configure<ConnectionStrings>(connectionSection);
services.AddScoped<IAdoSqlService, AdoSqlService>();
services.AddControllersWithViews();
services.AddHttpContextAccessor();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSession();
// Load the Federation configuration section from app settings
var federationConfig = Configuration.GetSection("Federation");
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromHours(2);//default is 14days
options.SlidingExpiration = true;// default
options.AccessDeniedPath = "/Error/AuthenticateError";// set a custom error access denied error page. this would need to be created/handled in your app.
})
.AddOpenIdConnect(options =>
{
//Set Options here......
//optional customizations to the auth and failure events
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = context =>
{
return Task.CompletedTask;
},
OnRemoteFailure = context =>
{
// handle an error response from Federation and redirect the user to a custom error page instead
context.Response.Redirect("/Error/401");
context.HandleResponse();
return Task.CompletedTask;
},
OnTokenValidated = async ctx =>
{
// This is the ClaimsIdentity created by OpenID Connect, you can add claims to it directly
ClaimsIdentity claimsIdentity = ctx.Principal.Identities.FirstOrDefault();
string userntid = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "preferred_username").Value;
string username = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "name").Value;
int isUser = 0;
int isAdmin = 0;
try
{
var db = ctx.HttpContext.RequestServices.GetRequiredService<IAdoSqlService>();
isUser = db.isUser(userntid);
isAdmin = db.isAdmin(userntid);
}
catch (Exception ex)
{
string error = ex.Message;
}
AppHttpContext.Current.Session.SetString("IsUser", "false");
if (isUser > 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "user"));
AppHttpContext.Current.Session.SetString("IsUser", "true");
}
AppHttpContext.Current.Session.SetString("IsUserAdmin", "false");
if (isAdmin > 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
AppHttpContext.Current.Session.SetString("IsUserAdmin", "true");
}
if (isUser == 0 && isAdmin == 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "not authorized"));
}
}
};
});

MQTT certificate-based authentication in flutter web and mobile

I am trying to use MQTT in a Flutter project to test the prototype. And I am using custom certificate-based authentication.
I am using mqtt_client package.
before connect SecurityContext giving this error in web Unsupported operation: default SecurityContext getter
below is my sample code
try {
SecurityContext securityContext =
SecurityContext(withTrustedRoots: false);
securityContext.setTrustedCertificatesBytes(caCertificate);
securityContext.useCertificateChainBytes(caCertificate);
securityContext.setClientAuthoritiesBytes(clientCertificate,
password: String.fromCharCodes(privateKey));
} catch (e) {
print(e);
}
_client = MqttClient('m.test.com', _identifier);
_client.port = 8883;
_client.keepAlivePeriod = 20;
_client.onDisconnected = onDisconnected;
_client.secure = true;
//_client.securityContext = securityContext;
_client.onBadCertificate = ((X509Certificate cert) => false);
_client.logging(on: true);```
this worked for me but I don't understand what (dynamic a) is doing ... perhaps someone can explain it in more detail ?
void initializeMQTTClient() async {
_client = MqttServerClient(_host, _identifier);
_client.port = 8883; //1883
_client.keepAlivePeriod = 20;
_client.onDisconnected = onDisconnected;
_client.secure = true; // false
_client.logging(on: true);
_client.securityContext = SecurityContext.defaultContext;
// _client.useWebSocket = false;
/// Add the successful connection callback
_client.onConnected = onConnected;
_client.onSubscribed = onSubscribed;
_client.onBadCertificate = (dynamic a) => true;

WSFederation ADFS login loop .Net Core 2.1

While logging into ADFS(on Premise), I am being redirected after I enter in my credentials and eventually it erros out with the error Exception details: Microsoft.IdentityServer.Web.InvalidRequestException: MSIS7042: The same client browser session has made '6' requests in the last '7' seconds. Contact your administrator for details in Event Viewer. I followed couple of stack overflow suggestion(link) but couldn't resolve the issue. I am running on https and made sure the certificate are right.
Here is my code
namespace TestApp
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
private static string HtmlEncode(string content) =>
string.IsNullOrEmpty(content) ? string.Empty : HtmlEncoder.Default.Encode(content);
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
options.Secure = CookieSecurePolicy.SameAsRequest;
});
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.Cookie.Name = ".AspNet.SharedCookie";
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.SameSite = SameSiteMode.None;
})
.AddWsFederation(options =>
{
options.MetadataAddress =
$"https://adfsdomain/FederationMetadata/2007-06/FederationMetadata.xml";
options.Wtrealm = "urn:apptest";
options.Wreply = "https://appdomain/apptest";
options.CallbackPath = "/apptest";
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "https://adfsdomain/adfs/services/trust/"
};
options.SaveTokens = true;
options.RequireHttpsMetadata = false;
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddMvc();//.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseCors(policy => policy.SetIsOriginAllowed(origin => origin == "https://adfsdomain"));
app.UseHttpsRedirection();
app.UseStaticFiles();
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
await context.ChallengeAsync(WsFederationDefaults.AuthenticationScheme);
}
else
{
await next();
}
});
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
I am using .Net Core 2.1 and ADFS 3.0. If I take ASP.NET MVC app and publish it with the same adfs setting my code works like charm, which tells me that the configuration on ADFS has been configured correctly.
Not sure if this can help you guys but I got my ADFS to working with the following:
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddWsFederation(options =>
{
options.Wtrealm = this._Configuration["wsfed:realm"];
options.MetadataAddress = string.Format("{0}/federationmetadata/2007-06/federationmetadata.xml", this._Configuration["wsfed:metadata"]);
})
.AddCookie();
You need app.UseAuthorization() before the app.Use(async ...). That's the middleware that takes the headers from the redirect and populates the context.User.

Two factor auth with IdentityServer3 - remember browser

I'm implementing 2fa with IdentityServer3 + Asp.Net Identity (2.2.1). I'm stuck on the 2fa implementation. I've looked at the "AspNetIdentity_2fa" sample, which helped a lot.
I have everything wired up, except for the cookie that indicates the browser has been successfully authenticated. I can set the cookie during the code confirmation, but I cannot get to the cookie in the PostAuthenticateLocalAsync() call to see whether or not to take the 2fa path.
protected override Task<AuthenticateResult> PostAuthenticateLocalAsync(User user, SignInMessage message)
{
if (user.TwoFactorEnabled) // && !TwoFactorCookieSet...
{
return Task.FromResult(new AuthenticateResult("/auth/sendcode", user.Id, user.DisplayName));
}
return base.PostAuthenticateLocalAsync(user, message);
}
I believe I'm taking the correct approach in using the partial logins, but how would I detect that the current browser has already been approved?
More detail: the /auth/sendcode is the standard Asp.Net Identity pages/flow for 2fa, combined with the partial login logic from the sample.
Okay, I found that OwinEnvironmentService can be injected into IdentityServer services. I can get the cookies via OwinEnvironmentService. I'd be interested to hear any opinions on this solution (this isn't meant to be production-ready, it's just a concept):
internal class UserService : AspNetIdentityUserService<User, string>
{
private readonly OwinEnvironmentService _owinEnvironmentService;
public UserService(UserManager userMgr, OwinEnvironmentService owinEnvironmentService) : base(userMgr)
{
_owinEnvironmentService = owinEnvironmentService;
DisplayNameClaimType = IdentityServer3.Core.Constants.ClaimTypes.Name;
}
protected override Task<AuthenticateResult> PostAuthenticateLocalAsync(User user, SignInMessage message)
{
if (user.TwoFactorEnabled)
{
var twoFactorNeeded = false;
object httpContext;
if (_owinEnvironmentService.Environment.TryGetValue("System.Web.HttpContextBase", out httpContext))
{
var cookies = (httpContext as HttpContext)?.Request.Cookies;
if (cookies != null && !cookies.AllKeys.Contains(IdentityConstants.CookieNames.TwoFactorCompleted)) twoFactorNeeded = true;
}
if (twoFactorNeeded)
return Task.FromResult(new AuthenticateResult("/auth/sendcode", user.Id, user.DisplayName));
}
return base.PostAuthenticateLocalAsync(user, message);
}
}
UPDATED
Based on Brock's comment, I think I have a better solution.
// custom User Service
internal class UserService : AspNetIdentityUserService<User, string>
{
private readonly OwinEnvironmentService _owinEnvironmentService;
public UserService(UserManager userMgr, OwinEnvironmentService owinEnvironmentService) : base(userMgr)
{
_owinEnvironmentService = owinEnvironmentService;
DisplayNameClaimType = IdentityServer3.Core.Constants.ClaimTypes.Name;
}
protected override async Task<AuthenticateResult> PostAuthenticateLocalAsync(User user, SignInMessage message)
{
if (user.TwoFactorEnabled)
{
var owinContext = new OwinContext(_owinEnvironmentService.Environment);
var result = await owinContext.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
if(result == null) return new AuthenticateResult("/auth/sendcode", user.Id, user.DisplayName);
}
return await base.PostAuthenticateLocalAsync(user, message);
}
}
// (in MVC controller) generate the 2FA security code and send it
public async Task<ActionResult> SendCode(SendCodeViewModel model)
{
// ...some code removed for brevity...
var token = await UserManager.GenerateTwoFactorTokenAsync(userId, model.SelectedProvider);
var identityResult = await UserManager.NotifyTwoFactorTokenAsync(userId, model.SelectedProvider, token);
if (!identityResult.Succeeded) return View("Error");
return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, model.ReturnUrl, model.RememberMe });
}
// (in MVC controller) verify the code and sign in with 2FA
public async Task<ActionResult> VerifyCode(VerifyCodeViewModel model)
{
// ...some code removed for brevity...
var signInManager = new SignInManager<User, string>(UserManager, Request.GetOwinContext().Authentication);
if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, model.Provider, model.Code))
{
await UserManager.ResetAccessFailedCountAsync(user.Id);
await signInManager.SignInAsync(user, model.RememberMe, model.RememberBrowser);
var resumeUrl = await env.GetPartialLoginResumeUrlAsync();
return Redirect(resumeUrl);
}
else
{
await UserManager.AccessFailedAsync(user.Id);
ModelState.AddModelError("", "Invalid code.");
return View(model);
}
}
I implemented the same for remember browser requirement however following statement return always null when we logout and login again.so twofactory step is not skipped..
var result = await owinContext.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

Using Autofac with webapi and mvc5.1 not working for webapi

I have a project using both mvc and webapi.
It's a Membership Reboot application so I have taken the example single application project and have slightly modified it to suit.
The DI works ok for controllers however when I try to call a webapi controller I keep getting an error
Make sure that the controller has a parameterless public constructor.
Is there something else I need to do for using autofac with webapi?
This is the code from my startup.cs
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "External",
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive
});
ConfigureMembershipReboot(app);
}
private static void ConfigureMembershipReboot(IAppBuilder app)
{
System.Data.Entity.Database.SetInitializer(new System.Data.Entity.MigrateDatabaseToLatestVersion<DefaultMembershipRebootDatabase, BrockAllen.MembershipReboot.Ef.Migrations.Configuration>());
//System.Data.Entity.Database.SetInitializer(new System.Data.Entity.CreateDatabaseIfNotExists<DefaultMembershipRebootDatabase>());
var cookieOptions = new CookieAuthenticationOptions
{
AuthenticationType = MembershipRebootOwinConstants.AuthenticationType
};
BuildAutofacContainer(app, cookieOptions.AuthenticationType);
app.UseMembershipReboot(cookieOptions);
}
private static void BuildAutofacContainer(IAppBuilder app, string authType)
{
var builder = new ContainerBuilder();
var config = CreateMembershipRebootConfiguration(app);
builder.RegisterInstance(config).As<MembershipRebootConfiguration>();
builder.RegisterType<DefaultUserAccountRepository>()
.As<IUserAccountRepository>()
.As<IUserAccountQuery>()
.InstancePerLifetimeScope();
builder.RegisterType<UserAccountService>().OnActivating(e =>
{
var owin = e.Context.Resolve<IOwinContext>();
var debugging = false;
#if DEBUG
debugging = true;
#endif
e.Instance.ConfigureTwoFactorAuthenticationCookies(owin.Environment, debugging);
})
.AsSelf()
.InstancePerLifetimeScope();
builder.Register(ctx =>
{
var owin = ctx.Resolve<IOwinContext>();
return new OwinAuthenticationService(authType, ctx.Resolve<UserAccountService>(), owin.Environment);
})
.As<AuthenticationService>()
.InstancePerLifetimeScope();
builder.Register(ctx=>HttpContext.Current.GetOwinContext()).As<IOwinContext>();
builder.RegisterControllers(typeof(Startup).Assembly);
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
var container = builder.Build();
System.Web.Mvc.DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
It was a 1 liner :)
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);