login/ logout implementation using cache and interceptor in spring - spring-mvc

I want to implement login/logout in my web application, I am using Spring MVC , and for login / logout i am using Ehcache and HandlerInterceptorAdapter. The basic idea is:
when user login -> authenticate with DB and if all the credentials
are correct then store the user object in cache with a key and store
the key in request object.
From the next request get the key from request object in preHandle()
method of HandlerInterceptorAdapter and check in cache if exist, or
redirect to login page.
code for storing in cache is:
if(validateUserFromDB()){
/* if a valid user */
userDtlForm = iHomePageService.getUserDetails(emailIdOfUser);
String token = generateSomeUniqueTokenForTheUser();
/* put the user in cache : [ key --> token || value--> userDtlForm ] */
storeTheTokenInCache();
request.setAttribute("token ", token );
}
now for every other request i am checking the cache against the token in my interceptor.
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getAttribute("token");
/* isValid == true when user exists inthe cache */
boolean isValid = checkCacheForTheToken(token);
if (!isValid) {
//redirect to login page
return false;
}
return true;
}
but the problem is I am getting null value by request.getAttribute("token") in my interceptor.what i am doing wrong

when user login -> authenticate with DB and if all the credentials
are correct then store the user object in cache with a key and store
the key in request object.
From the next request get the key from request object in preHandle() method of HandlerInterceptorAdapter and check in cache if exist, or redirect to login page.
In postHandle() again set the token back to client.
maintain the token in a common jsp, thus it can be attached with all request.
What is the way to implement it, please suggest ..../

Related

Best practice: Redirecting to client error page on failed identity server login

My security architecture consists of an Identity Server, JS Client and an external provider. The Identity Server uses authorization code flow.
A simplified login process looks as follows: User clicks login in the JS Client -> redirected to Identity Server -> redirected to external provider. I then use the external provider to authenticate the user. When sucessfully redirected back to my Identity Server I then check in a database if the user is active or not. This happens in the AccountController:
[HttpGet]
[Route("callback")]
public async Task<IActionResult> Callback()
{
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
...
var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";
// My check if user is active or not
var isActive = await IsActiveAsync(providerUserId);
if (!isActive)
{
var errorRedirectUri = "https://127.0.0.1/error_page";
return Redirect(errorRedirectUri);
}
var context = await Interaction.GetAuthorizationContextAsync(returnUrl);
await Events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, providerUserId, subjectId, true, context?.Client.ClientId));
return Redirect(returnUrl);
}
If the user is active, the flow works perfectly. Access and refresh tokens are created, user is signed into the Identity Server and redirected back to the client. I am having issues in handling the case, where the user sucessfully logged in to the external provider but is not active in my database. In this case I want to redirect to a error page of the client.
An obvious way to do it, is to configure a error redirect uri, to which the user is redirected to in the mentioned scenario. But this does not feels like the proper way.
I also considered implementing the ProfileService.IsActiveAsync(...) method, but this just resulted in redirecting the user back to the external provider login after setting context.IsActive = false;. With no option of returning to the JS Client without valid credentials of an active user.
public async Task IsActiveAsync(IsActiveContext context)
{
// My check if user is active or not
context.IsActive = await IsActiveAsync(context.Subject);
}
So my question is: What is the apropriate way of redirecting the user to a client-side error page when the user was sucessfully authenticated at the external provider but was not marked as active in my own database?

SignInManager.SignInPasswordAsync is not logging in the User in ApiController

I have an ApiController that handles ajax login requests. Below is what is happening in the code:
// I tried with HttpContext.Current.GetOwinContext().Get... and it didn't work either
private ApplicationUserManager userManager => Request.GetOwinContext().Get<ApplicationManager>();
private ApplicationSignInManager signInManager => Request.GetOwinContext().Get<ApplicationSignInManager>();
public async Task<IHttpActionResult> Login(LoginJson request)
{
var user = await userManager.FindByEmailAsync(request.Email);
var result = await signInManager.PasswordSignInAsync(user.UserName, request.Password, true, true);
if (result == SignInStatus.Success)
{
if (!User.Identity.IsAuthenticated)
{
throw new Exception("Why are you not authenticating!!!");
}
}
return Ok();
}
That exception is always thrown (i.e. the result is Success and yet IPrincipal reports that user is not authenticated yet).
What is even weirder is that if I reload the page I am taken to the dashboard page (Dashboard is the default home page for authenticated users), meaning the previous request actually did log the user in. But then why would User.Identity.IsAuthenticated return false in my first login request? Any ideas?
Note: this is only happening for ApiController, a normal MVC Controller logs the user in correctly
Authentication cookie is only set when your controller send a reply to a client (browser). And User.Identity.IsAuthenticated is checking if the cookie is set. But you are trying to check if the cookie is set within the same request as where you set the cookie.
In other words you can only check if user is authenticated only on the following request after you call PasswordSignInAsync. So remove that throw new Exception... and you'll be fine.

Using Facebook access tokens obtained in mobile app to access ASP.Net WebAPI2 controller actions

Setup
Client: mobile app built on Cordova
Backend: ASP.net WebAPI2 (based on the standard template) configured with facebook login provider.
Problem
Having authenticated the user in the mobile app and received a Facebook access token, I pass this in subsequent requests as an HTTP header ("Authorization: Bearer "). This returns status 401 Unauthorized.
Questions
What am i missing here? How can i access the WebAPI controller actions based on the Facebook access token obtained on the mobile device?
On a high level, what i'm trying to achieve is this:
User opens mobile app and authenticates with Facebook
If user is not registered as a local user, he must choose a username to complete the registration
User is registered and can access API
I was facing the same problem and I found a really good solution here: http://codetrixstudio.com/mvc-web-api-facebook-sdk/
The WebApi web site can't understand the access token provided by Facebook. I guess it's because it hasn't been issued by itself (LOCAL AUTHORITY) but by an external provider. The approach explained in the link above is based on validating the token given by Facebook using it's API and recreating the access token.
So, you'll need some additional steps to achieve your goal.
The external providers have API so you can get information. For example, the https://graph.facebook.com/me?access_token={0} can be used to check if the token is valid. On the server side, you'll need to make a https web request to this URL passing the token (and the secret app as a proof, if the app is configured to ask it in Facebook).
Given the token is ok, you'll create an identity (ClaimsIdentity) using the information you've got at the API (id and username, for example). This identity will be needed to make an instance of the AuthenticationTicket class so you'll be able to issue a new access token for your Cordova app. Use this new bearer access token in the Authorization header of your https calls and your WebApi will recognized it as valid calls.
Ps. The good thing here is that you can set the token's expiration.
Since API's are stateless, there are multiple ways to secure it. In this case the mobile app has authenticated the user, but the API has not.
You can register the user's email and facebook ID into the database using a anonymous route. this can serve as both the login and register technically. (you could secure it with a clientid via OAuth if you don't want it fully open) along with thier current token. You verify the user against the facebook API on the server before registering of course just in case.
Create a custom route handler to secure the account controller or any other routes. The custom route handler would check the database for the current FB token and fb ID combo as well as token expire time, since you don't want to keep authenticating if it's expired.
Facebook has two types of tokens.
A Short lived token, initially created when you login and a long term token that lasts up to 60 days vs 1-2 hours.
for the api side, i suggest sticking to the short lived token and re-authenticate once expired but thats up to you.
You should grab a facebook SDK of your choice to verify and pull account info.
Example Secured Route:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "routename",
routeTemplate: "account",
constraints: null,
handler: new CustomFBHandler()
{
InnerHandler = new HttpControllerDispatcher(config)
},
defaults: new {controller = "default"}
);
}
Custom Handler: (note that you should pass any needed dependancies in the construtor)
public class CustomHandler : DelegatingHandler
{
public CustomHandler()
{
}
protected async Task<bool> IsAuthenticated(HttpRequestMessage requestMessage)
{
//Authenticate FB User Info HERE Against the Registered/logged in user....
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
bool isAuthenticated = false;
try
{
isAuthenticated = await IsAuthenticated(request);
}
catch (Exception e)
{
var response = request
.CreateResponse(HttpStatusCode.InternalServerError, new { status = new { code = 333, error = true, message = e.Message } }, new JsonMediaTypeFormatter());
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(Configuration.AuthenticationScheme));
return response;
}
if (!isAuthenticated)
{
var response = request
.CreateResponse(HttpStatusCode.Unauthorized,new {status=new{code=1,error=true,message="Authorization Failed"}},new JsonMediaTypeFormatter());
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(Configuration.AuthenticationScheme));
return response;
}
return await base.SendAsync(request, cancellationToken);
}
}
You should send the FB Token and Facebook user id in the Headers. Once authenticated you can use the token/id to pull the user info you need from the database.

ASP.NET Web API how to authenticate user

I'm trying to create a simple user authentication function but I just can't get it to work.
Here is the code I'm working on:
public class LoginController : ApiController
{
private void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
public bool Login(string token)
{
//Check token
if (.....)
{
//Authenticate user
var identity = new GenericIdentity("Test user");
SetPrincipal(new GenericPrincipal(identity, new string[]{"Test role"}));
}
}
[Authorize]
public string TestFun()
{
return "Hello " + User.Identity.Name;
}
}
So, if I try to call method TestFun() first, it returns error code 401 like it should.
However when I call method Login() it should somehow save user credentials, but this is where I get lost, I just can't get it to work.
TestFun() always returns error code 401 even if I call Login() first.
If I try to put return "Hello " + User.Identity.Name; in the Login() function it returns correct username, but in the TestFun() the user is not available.
I've even tried using Sessions and FormsAuthentication but I just can't get it to work, even on this really simple example.
Can someone please tell me what am I missing?
Thanks!
The Login method sets the principal for current request only. Just after the request completes, the principal context is wiped out so that the server can handle other requests for other users. When a new request comes, eons later from the server perspective, the principal context no longer exists and if nothing restores it, the request is unauthenticated.
To fix this you have to return something from your login method to the client. Not only bool but rather - an authentication token. Something the client could use to authenticate further requests.
It could be anything. Forms cookie would be fine as long as the client remembers to append it to further requests. Another common practice is to have a custom authentication token returned to the client and then appended by the client in a custom authentication header. And as forms cookies are handled by the Forms Authentication module, custom headers would need a custom mvc authentication filter or custom asp.net authentication module so that the token is readed, the identity is extracted and restored just before the request is about to execute.
If you don't like to bake your own token infrastructure, I would also recommend OAuth2 tokens. There is a great book that contains easy to follow examples on this and other possible authentication methods:
http://www.amazon.com/Pro-ASP-NET-Web-API-Security/dp/1430257822/ref=sr_1_1?ie=UTF8&sr=8-1&keywords=web+api+security
I just got the same issue, yes, I agreed we need to save that principal into somewhere (cookie, session) for other action to use, so, in SetPrincipal function I added
HttpContext.Current.Session["user"] = HttpContext.Current.User;
Now, the issue is how to get it back for other action, the idea popups in my mind is to extend AuthorizeAttribute and override IsAuthrized function, it will read the session first and if it found the session, it will return true, otherwise it will return false.
namespace BinZ
{
public class MyAuthorizeAttribute:AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext) {
HttpContext.Current.User = HttpContext.Current.Session["user"] as IPrincipal;
return HttpContext.Current.User != null;
}
}
}
Please remember to replace [Authorize] to [MyAuthorizeAttribute] in WebApi controller.
It works for me very well.
Cheers

Prevent overlapping service calls from ASP.NET application when renewing service session

I have an ASP.NET web application that presents data retrieved from a web service.
Before getting actual data from the service a token must be retrieved by calling a specific method in that service. This token must then be providing in any subsequent data method calls. This token expires after some time and a call to a data method with an expired token leads to an error and a new token must be retrieved for further communication. The active token is kept in a global ASP.NET application variable.
This is all fine, but how do I prevent overlapping token renewals? A request from one user to the site triggers a service call, this gives an error and a new token is retrieved and put in the cache, but in the meantime another user request finds that the token is expired and also renews the token.
Is there not a problem here? I can't really wrap my head around it.
You can do so by locking (Expiration = 2 hours):
static class TokenGenerator
{
private static object tokenLock = new object();
private static DateTime tokenExp = DateTime.MinValue;
private static string token = null;
public static string GetToken()
{
if(tokenExp >= DateTime.UtcNow)
return token;
lock(tokenLock)
{
if(tokenExp >= DateTime.UtcNow)
return token;
// generate token
token = GenerateToken();
tokenExp = DateTime.UtcNow.AddHours(2);
}
}
}
public static string GenerateToken()
{
// generate and return token
}
Having a global token like this might not be best practice though.
Also this code blocks all other token requests while the token is being regenerated, depending on how long the token generation takes this might not be acceptable.

Resources