MVC Form based Spring Security login? - security

I have an application that uses Spring Security to control access to pages, to manage user roles (GrantedAuthority) and for ACL. The application uses the standard UsernamePasswordAuthenticationFilter that intercepts requests to /j_spring_security_check (with j_username and j_password request parameters), and using a ProviderManager it authenticates the user and on success stores it in the SecurityContextHolder.
The above is configured in the security context, using a customized UserDetailsService:
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>
The above approach in my case is not optimal, for the following reasons:
Adding a captcha requires extra filters
In order to customize the login logic, I need to replace the AuthenticationProvider as well
showing errors in the login form is complex, since I cannot use Spring MVC's forms
My idea is to remove the interceptor based login and put all the logic inside a Spring 3 MVC controller. The pseudo-code is as following:
RequestMapping(value="/login/", method = RequestMethod.POST)
public String attemptLogin(HttpServletRequest request, HttpServletResponse response,
#ModelAttribute("login") LoginCmd login, Model model) {
// validate command (username, password, captcha)
// ...
// load user from DB
User user = userService.loadUserByUsername(login.getUsername());
// extra logic (check number of failed logins + other stuff)
// ...
// In case everything is fine, create a spring security User
/* Instead of creating the user, read it from DB */
org.springframework.security.core.userdetails.User authUser =
new org.springframework.security.core.userdetails.User(
login.getUsername() /*username*/,
login.getPassword() /*password*/,
true /*enabled*/,
true /*accountNonExpired */,
true /*credentialsNonExpired */,
true /*accountNonLocked*/,
new ArrayList<GrantedAuthority>() /*authorities*/
);
// build the AuthenticationToken
UsernamePasswordAuthenticationToken authResult =
new UsernamePasswordAuthenticationToken(authUser, login.getPassword(),
authUser.getAuthorities());
// use WebAuthenticationDetailsSource do build details
authResult.setDetails(detailsSource.buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authResult);
return SUCCESS_VIEW;
}
Do you see any problem with the solution here above? Is setting the authentication inside the SecurityContextHolder enough? Am I missing something?
Comments and suggestions are welcome ;-)
Thanks a lot to everyone
Andrea

I went through the Spring Security code, and on successful authentication also the original code just stores the Authentication object in the SecurityContextHolder, nothing else is done.
For example, in class AbstractAuthenticationProcessingFilter (which is used by the standard login intercepting requests to /j_spring_security_check) does that:
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
Authentication authResult) throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
successHandler.onAuthenticationSuccess(request, response, authResult);
}
I implemented this on my application and everything works fine.

Related

Preventing ClaimsTransformation from running on every HTTP request

I have a web application targeting .NET 5.0 and I am trying to implement Windows Authentication along with some policies for authorization. We have a database table from another application that holds info on user roles, so I am using that to set permissions for my users.
I created a ClaimsTransformer class:
ClaimsTransformer.cs
public class ClaimsTransformer : IClaimsTransformation
{
// snip constructor which pulls in my DbContext from DI
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var id = ((ClaimsIdentity) principal.Identity);
var ci = new ClaimsIdentity(id.Claims, id.AuthenticationType, id.NameClaimType, id.RoleClaimType);
// snip call to DbContext to get user's role from database
if (roleId == 1 || roleId == 7)
{
ci.AddClaim(new Claim("user-role", "admin"));
}
return new ClaimsPrincipal(ci);
}
}
I have my authentication/authorization setup like this:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
services.AddAuthorization(options =>
{
options.AddPolicy("Admin", policy =>
policy.RequireClaim("user-role", "admin"));
});
// snip rest of method
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// snip unrelated code
app.UseAuthentication();
app.UseAuthorization();
}
My first use of this is to hide a menu in my _Layout.cshtml:
#if ((await AuthorizationService.AuthorizeAsync(User, "admin").Succeeded)
{
// Admin nav link
}
The issue is, since AuthorizeAsync is running on every HTTP request, my ClaimsTransformer also runs each time, hitting the database to check the user's roles on every request. I'd like to avoid this, but I'm not sure of the best way to do so.
Basically, I'd like the system to check the roles only once, when the user is first authenticated. I read that this is what is supposed to happen when using Windows Authentication with IIS, but I am seeing the roles query running on every request when deployed to my IIS server also.
I could easily add a check in my ClaimsTransformer to see if the "user-role" claim exists, and only hit the DB if it is not present, but is there a better way? Should I be overriding something like UserClaimsPrincipalFactory instead of using a ClaimsTransformer?

Basic authentication for website to secure site login and API access

I'm looking at the security model of a website that's being developed. After researching the web i have found that there are several security models to secure websites i.e. Basic Auth, JWT ...
At the moment, SSL is not enabled as still in dev. Website has a login page and communicates via API's (including login and logout). On the login page, as a test, I have attempted to log in with false details, and then I have looked at the developer tools to identify the security mechanism and found the following screenshots. I think the site is using basic authentication, though I noted that the email / password is not encoded and is using a custom login form. Could someone confirm if it is basic authentication being utilised?
Developer Tools Images
[Request Header Image][2]
UPDATE:
I discovered that once the user is authenticated by email/password, I should have posted the screenshots as this is where keys are returned. In the below screenshot a bidder token and bidder secret is sent back to client. I think these are generated through crypto on backend. So I don't think its JWT, but is this a suitable way in creating keys and not sending in header but in response body?
Network tab after user logged in
Login Form Code :
{
/* prepare ui */
progress.classList.remove('hide');
login_btn.innerText = 'Logging In';
login_btn.setAttribute('disabled', true);
/* make http request */
var http = new XMLHttpRequest();
var url = SERVER + '/api/bidder/login';
var body = {
email: email.value,
password: password.value
};
http.open('POST', url, true);
http.setRequestHeader('Content-type', 'application/JSON');
http.onreadystatechange = function () { //Call a function when the state changes.
if (http.readyState == 4 && http.status == 200) {
var res = JSON.parse(http.responseText);
if (res.status) {
localStorage.setItem("bidData", JSON.stringify(res.data));
window.location.href = window.location.href.replace('login.html','');
} else {
Toast.show('Danger', res.message);
}
/* reset ui */
progress.classList.add('hide');
login_btn.innerText = 'Log In';
login_btn.removeAttribute('disabled');
}
}
http.send(JSON.stringify(body));
}
When you use basic access authentication, credentials wouldn't be loaded in a request payload. They reside in an authorization header like "Authorization: Basic ~some credential here~".
So if you neither see this authorization header in your request nor a popup like below on the website, basic access authentication is not enabled.
Spring security is the most basic authentication in the Spring project
If you want to enable Spring security, the first thing you must add is the spring security library to your project. After adding it, you create a class to configure Spring security.
A function in the class config for Spring security.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/",
"/api/statistical/**",
"/static/**",
"/webjars/**",
"/img/**",
"/css/**",
"/js/**",
"/api/diary/**")
.permitAll()
.antMatchers("/api/auth/**")
.permitAll()
.antMatchers("/api/user/checkUsernameAvailability", "/api/user/checkEmailAvailability")
.permitAll()
.antMatchers(HttpMethod.GET, "/api/users/**") //, "/api/polls/**"
.permitAll()
.anyRequest()
.authenticated();

External Login WebAPI2 MVC5

I need to use a 3rd party token as a way to authenticate. I'm getting the token in the home controller and need to be able to send it on to my WebAPI controllers (It's an SPA application using Backbone). Is there a way of doing this?
EDIT:
Relevant code:
public ActionResult Index(string projectId, int companyId, bool isCompanyAdmin)
{
// if not a valid user return error
var validate = new Validate().ValidateContext(HttpContext,
"Key", "Secret");
if (!validate.IsValidated) return View(Constants.ValidationFailed);
// The info validated, so now I can set it to authorized
// put code here for doing it
//Get the model for the user
try
{
var model = ConvertToVM(_smsRepository.GetCompany(companyId, projectId));
}
catch (ProviderIncompatibleException)
{
// connection string wrong
return View(Constants.ConnectionFailed);
}
catch (Exception e)
{
// catch all
return View(Constants.DatabaseError);
}
//create and send through the view model that determines what view the user will get
return View(model);
}
Ok I put in the index method on the Home Controller. Like I said, we make a call to a third party API passing in the context, the client key, and the client secret to verify our identity. Could I just add a Bearer token in the home controller? Or otherwise pass the http context to OWiN and use some custom logic to add the token if validate.IsValidated is true? It needs to be something that works with WebAPI.

Swagger authentication in Azure App Service

In my Azure Mobile .NET backend I want to use Azure Mobile .NET Server Swagger . I'm looking for fast way to hide swagger UI from public access ? Is there any way to provide access only for selected users ?
First a disclaimer: Even if you protect your Swagger UI from public consumption, you are not protecting your APIs from public consumption. You have to assume that everyone knows all of your routes and have the appropriate security in place to protect any requests that may come in.
That being said, there's still not a simple way to do this. Swashbuckle (the piece that adds Swagger to Web API) adds a custom HttpMessageHandler to the /swagger/ui route (as seen here). If you look at the Web API pipeline, you can see that if you specify a custom handler, you can bypass all of the Controller selection, Auth filters, etc. This is what happens here.
Some solutions:
Use an app setting to conditionally call ConfigureSwagger(config) in debug modes only. This would prevent all /swagger routes from making it into production. Or you could use a staging slot and only add it there.
You can wrap the SwaggerUiHandler with something like this Basic Auth MessageHandler. This would prompt the user for basic creds if they went to the /swagger/ui route. See below for my modified version of this code.
Maybe with a little more thought we can come up with a better solution -- I see a couple of issues (here and here) in the Swashbuckle repo that indicate you're not the first one to hit this.
Modified BasicAuthHandler (from here):
Warning: minimally tested (and be sure to change how you verify user/pass)
public class BasicAuthMessageHandler : DelegatingHandler
{
private const string BasicAuthResponseHeader = "WWW-Authenticate";
private const string BasicAuthResponseHeaderValue = "Basic";
public BasicAuthMessageHandler(HttpMessageHandler innerHandler)
{
this.InnerHandler = innerHandler;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
AuthenticationHeaderValue authValue = request.Headers.Authorization;
HttpResponseMessage unauthorizedResponse = request.CreateUnauthorizedResponse();
if (authValue != null && !string.IsNullOrWhiteSpace(authValue.Parameter))
{
Credentials parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
if (parsedCredentials != null)
{
// TODO: Check that the user/pass are valid
if (parsedCredentials.Username == "user" &&
parsedCredentials.Password == "pass")
{
// If match, pass along to the inner handler
return base.SendAsync(request, cancellationToken);
}
}
}
else
{
// Prompt for creds
unauthorizedResponse.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
}
return Task.FromResult(unauthorizedResponse);
}
private Credentials ParseAuthorizationHeader(string authHeader)
{
string[] credentials = Encoding.ASCII.GetString(Convert
.FromBase64String(authHeader))
.Split(
new[] { ':' });
if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0])
|| string.IsNullOrEmpty(credentials[1])) return null;
return new Credentials()
{
Username = credentials[0],
Password = credentials[1],
};
}
}
Registering with Swagger route
// Do this after calling ConfigureSwagger
ConfigureSwagger(config);
// Remove the swagger_ui route and re-add it with the wrapped handler.
var route = config.Routes["swagger_ui"];
config.Routes.Remove("swagger_ui");
config.Routes.MapHttpRoute("swagger_ui", route.RouteTemplate, route.Defaults, route.Constraints, new BasicAuthMessageHandler(route.Handler));

Access to WebAPI 2.2 from mvc application after Logging In

I have MVC 5 application that uses Form Authentication (the same as in default MVC 5 project template). The same project contains WebAPI 2.2 controller that provides some API the application uses.
I would like to have two type of access to the API by using Bearer Tokens. The API will be invoked via javascript. I added code to do that using http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api.
The problem is that the example code gets token by making call to separate API and providing username and password. What I would like to achieve is to return Bearer Token after the user successfully logs in into MVC application and not by making another call in javascript.
Is this possible ?
Update:
I would like to return token in Login action of Account controller by using header. However probably because of redirections the header is discarded.
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
You can use Oauth authentication thereby which you can generate an access token and can be used the same for further requests to the web API. This access token can be saved in db or somewhere for using in future requests.

Resources