//Interface--Injecting into controller
public interface IContext
{
HttpRequestBase Request { get; }
HttpResponseBase Response { get; }
}
//Implementation
public class Context : IContext
{
public HttpRequestBase Request { get { return new HttpRequestWrapper(HttpContext.Current.Request); } }
public HttpResponseBase Response { get { return new HttpResponseWrapper(HttpContext.Current.Response); } }
}
//Setting Cookie on login.
SetCookie(_context, login);
public void SetCookie(IContext context, Login login)
{
var loginDetails = new JavaScriptSerializer().Serialize(login);
var cokie = new HttpCookie("login", login);
context.Response.Cookies.Add(cokie);
}
//Trying to Logout.
public ActionResult Logout()
{
foreach (var key in _context.Request.Cookies.AllKeys)
{
try
{
_context.Response.Cookies.Remove(key); //this didn't work
//tried this even this is not working
var login = new Login {Identity = "", LogIn = false};
_login.SetCookie(_context, login);
}
catch (Exception e)
{
}
}
_context.Response.Cookies.Clear(); //this didn't work either
return RedirectToAction("Index", "Login");
}
On Login Index when I check the current login cookie value it always has the value of the logged in user is just not getting set to null or empty.
I suspect there is something wrong in the Implementation of the IContext. Should there be a setter on it? Not sure..
Also Tried:
var cokie = context.Request.Cookies["login"];
cokie.Expires = DateTime.Now.AddDays(-2);
cokie.Value = null;
context.Response.Cookies.Add(cokie);
You can use the below code if you are using forms authentication. It will clear all the required cookies
FormsAuthentication.SignOut();
Session.Abandon();
Alternatively you can use
Session.Abandon();
Response.Cookies.Clear();
or
YourCookies.Expires = DateTime.Now.AddDays(-1d);
For more information please visit
MSDN Cookie Help
If to log user in you use
SignInManager.PasswordSignInAsync(...)
you can try
AuthenticationManager.SignOut();
instead of
_context.Response.Cookies.Clear();
Related
I cloned this github project: https://github.com/cornflourblue/dotnet-5-jwt-authentication-api
So I have followed function to validate the jtw token and on a successful jwt validation it attach user to context:
private void attachUserToContext(HttpContext context, string token)
{
try
{
//...
var jwtToken = (JwtSecurityToken)validatedToken;
var userId = jwtToken.Claims.First(x => x.Type == "username").Value;
// attach user to context on successful jwt validation
context.Items["User"] = userId;
}
catch
{
// do nothing if jwt validation fails
// user is not attached to context so request won't have access to secure routes
}
}
So the AuthorizeAttribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var username = context.HttpContext.Items["User"];
if (username == null)
{
// not logged in
context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
}
}
}
But for some reason it allows all requests, even requests without header information. If u need more information then just ask!
I dont know why the Custom AuthorizeAttribute dont work but I set the same
query to test that HttpContext.Items["User"] is not null and now it work...:
[Authorize]
[HttpGet("Select")]
public IActionResult Select()
{
try
{
if (HttpContext.Items["User"] == null)
{
return Conflict(new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized });
}
return Ok(TestService.Select());
}
catch (Exception ex)
{
return Conflict(ex);
}
}
Currently, I am working on a web application, based on the roles we have to display the controls in the UI. Roles are stored in the DB, whenever the user logs in, by fetching the user Id, I will hit the DB and get the user role and store it in the cookie. So, for the next request, I will fetch the user role from the User.IsInRole() and proceed with the logic same will happens in the view. This entire thing is working fine with the single server but when it comes to load balancer, this behaving weirdly and intermittently it's giving issue, as User.IsInRole() is returning false sometime.
The code in my controller:
public ActionResult IsValidUser(string userName)
{
try
{
if (HttpContext!=null&& HttpContext.Request.Headers["username"] != null)
{
userName = HttpContext.Request.Headers["username"];
}
//Get the roles by sending the user name
userRole = _processor.GetUserRole(userName);
UserViewModel user = new UserViewModel()
{
UserName = userName,
Role = userRole
};
if (!string.IsNullOrEmpty(user.Role))
{
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, user.UserName, DateTime.Now, DateTime.Now.AddMinutes(2880), true, user.Role, FormsAuthentication.FormsCookiePath);
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);
if (ticket.IsPersistent)
{
cookie.Expires = ticket.Expiration;
}
if(Response!=null)
Response.Cookies.Add(cookie);
if (!string.IsNullOrEmpty(Request.Form["ReturnUrl"]))
{
return View("Error");
}
else
{
return RedirectToAction("Index");
}
}
else
{
return View("Error")
}
}
else
return RedirectToAction("Search");
}
catch (Exception ex)
{
return View("Error);
}
}
The code in Global.asax.cs
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
string userData = ticket.UserData;
string[] roles = userData.Split(',');
HttpContext.Current.User = new GenericPrincipal(id, roles);
}
}
}
}
the code in my controller to do the logic:
public FileContentResult ViewAttachment(string attachmentName, int attachmentId)
{
if (ConfigurationManager.AppSettings["Environment"] != Constants.ProductionEnvironment ||
(User.IsInRole(Constants.Administrator) || User.IsInRole(Constants.Contributor) || User.IsInRole(Constants.Member)))
{
//Logic here
return File(bytes, mimeType);
}
else
{
_logger.WriteInformation("Not authorized");
return File(bytes, mimeType);
}
}
I am not sure what mistake is there but this is not working in load balancer sometimes it is showing "User is not authorized" but in actual user is authorized. Is it because of cookies or load balancer? Any help would be appreciated. Thanks in advance.
Here is my code. I have written login to validate the token, for valid token return user object. but unable to find way to make it available across controllers.
I don't want to use Identity.
public class CustomAuthorize : AuthorizeAttribute
{
private const string AUTH_TOKEN = "AuthToken";
public override Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
AllowAnonymousAttribute allowAnonymousAttribute = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().FirstOrDefault();
if (allowAnonymousAttribute != null)
{
return Task.FromResult<object>(null);
}
if (actionContext.Request.Headers.Contains(AUTH_TOKEN))
{
var authToken = actionContext.Request.Headers.GetValues(AUTH_TOKEN).First();
var user = Utility.GetUserByToken(authToken);
if (user != null)
{
//
// how to make this `user` object available across the controllers
//
return Task.FromResult<object>(null);
}
else
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, new CustomError() { Code = 100, Message = "Invalid Access Token" });
return Task.FromResult<object>(null);
}
}
else
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, new CustomError() { Code = 100, Message = "Invalid Access Token" });
return Task.FromResult<object>(null);
}
}
}
Please help...
Your Question is a bit unclear - I assume you are referring to this line:
var user = Utility.GetUserByToken(authToken);
If so, then I might have a solution. So the fundamental problem is that you cannot simply save this variable where you currently are in the current controller, you need to understand the context you are working in - Every time a different user makes a request a different user models get created in the current controller. To have access to the user model across my app whenever a user makes a request, I do the following:
First you need to hook into the request receiving process of ASP.NET. This can be done inside the Global.asax.cs file, but I prefer to keep it clean and create a PartialGlobal class and mark the Global.asax.cs as partial.
From
public class MvcApplication : System.Web.HttpApplication
To
public partial class MvcApplication : System.Web.HttpApplication
Then create the PartialGlobal Class:
public partial class MvcApplication
{
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
var request = HttpContext.Current.Request;
var authHeader = request.Headers["Authorization"];
//For API users
if (authHeader != null)
{
var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);
if (authHeaderVal.Scheme.Equals("Basic", StringComparison.OrdinalIgnoreCase))
{
if (!string.IsNullOrEmpty(authHeaderVal.Parameter))
{
AuthenticateUser(authHeaderVal.Parameter);
}
}
}
//For Regular Website Users
else
{
HttpCookie authCookie = request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
//Extract the forms authentication cookie
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
// If caching userData field then extract
var userModel = UsersBLL.DeserializeObject(authTicket.UserData);
var principal = new UserPrincipal(userModel);
SetPrincipal(principal);
}
}
}
private static bool AuthenticateUser(string credentials)
{
var model = UsersBLL.DecryptToken(credentials);
if (!model.RefUser.HasValue)
{
return false;
}
SetPrincipal(new UserPrincipal(model));
return true;
}
private static void SetPrincipal(UserPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
}
The UserPrincipal Class:
public class UserPrincipal : IPrincipal
{
public IIdentity Identity { get; private set; }
//Just a class with details like name,age etc.
public UserModel Model { get; set; }
public UserPrincipal(UserModel model)
{
this.Model = model;
this.Identity = new GenericIdentity(model.Email);
}
}
Note the line in the PartialGLobal class: var model = UsersBLL.DecryptToken(credentials);. Here I simply use a method I created to de-crypt my token string so it can be deserialized, you probably won't have/need this.
The essential part is this last step of the PartialGlobal class:
private static void SetPrincipal(UserPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
If you have the context know about your user, you can access it anywhere by simply calling:
var principal = (UserPrincipal)HttpContext.Current.User;
One way is to Extend the ApiController which is what your controllers are using as the base class.
Define a CustomController
public class CustomController : ApiController
{
projected User _user;
}
For all the other controller use this as a base class and the _user object should be accessible from all the controllers.
I want to do custom authentication because we have many controllers and it makes sense to create global filter that applies for all controllers and their actions with exception of login page.
In Global.asax.cs I added next global filter:
public class Global : HttpApplication
{
void Application_Start(object sender, EventArgs e) // Code that runs on application startup
{
... // only showing important part
GlobalFilters.Filters.Add(new Filters.AuthenticationUserActionFilter());
...
}
File AuthenticationUserActionFilter.cs:
public class AuthorizeUserActionFilter : System.Web.Mvc.Filters.IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousActionFilter), inherit: true) || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousActionFilter), inherit: true);
if (skipAuthorization) // anonymous filter attribute in front of controller or controller method
return;
// does this always read value from ASPXAUTH cookie ?
bool userAuthenticated = filterContext.HttpContext.User.Identity.IsAuthenticated;
if (!userAuthenticated)
{
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary() { { "controller", "Account" }, { "action", "Login" } });
return;
}
if( HttpContext.Current.User as Contracts.IUser == null )
{
// check if IUser is stored in session otherwise retrieve from db
// System.Web.HttpContext.Current.User is reseted on every request.
// Is it ok to set it from Session on every request? Is there any other better approach?
if (HttpContext.Current.Session["User"] != null && HttpContext.Current.Session["User"] as Contracts.IUser != null)
{
HttpContext.Current.User = HttpContext.Current.Session["User"] as Contracts.IUser;
}
else
{
var service = new LoginService();
Contracts.ISer user = service.GetUser(filterContext.HttpContext.User.Identity.Name);
HttpContext.Current.Session["User"] = user;
HttpContext.Current.User = user;
}
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext) {}
}
My login code is like this (in AccountController.cs):
[Filters.AllowAnonymousActionFilter]
[HttpPost]
public JsonResult Login(string username, string password, bool rememberMe = false)
{
LoginService service = new LoginService();
Contracts.IUser user = service .Login(username, password);
System.Web.HttpContext.Current.Session["User"] = value;
System.Web.HttpContext.Current.User = value;
// set cookie i.e. ASPX_AUTH, if remember me, make cookie persistent, even if user closed browser
if (System.Web.Security.FormsAuthentication.IsEnabled)
System.Web.Security.FormsAuthentication.SetAuthCookie(username, rememberMe);
return new SuccessResponseMessage().AsJsonNetResult();
}
Contracts.IUser interface:
public interface IUser : IPrincipal
{
Contracts.IUserInfo UserInfo { get; }
Contracts.ICultureInfo UserCulture { get; }
}
My question is this:
System.Web.HttpContext.Current.User is reseted on every request. Is it ok to set HttpContext.Current.User with Session value on every request? Is there any other better approach? What is best practise? Also Microsoft seems to have multiple ways of dealing with this problem (googled a lot of articles on this, also on stackoverflow Custom Authorization in Asp.net WebApi - what a mess?). There is a lot of confusion about this, although they developed a new Authorization in asp.net core.
One possible approach is to serialize the user as part of the UserData portion of the ASPXAUTH cookie. This way you don't need to fetch it from the database on each request and you don't need to use Sessions (because if you use sessions in a web-farm you will have to persist this session somewhere like in a database, so you will be round-tripping to the db anyway):
[Filters.AllowAnonymousActionFilter]
[HttpPost]
public JsonResult Login(string username, string password, bool rememberMe = false)
{
LoginService service = new LoginService();
Contracts.IUser user = service.Login(username, password);
string userData = Serialize(user); // Up to you to write this Serialize method
var ticket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddHours(24), rememberMe, userData);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket));
return new SuccessResponseMessage().AsJsonNetResult();
}
And then in your custom authorization filter you could decrypt the ticket and authenticate the user:
public void OnAuthentication(AuthenticationContext filterContext)
{
... your stuff about the AllowAnonymousActionFilter comes here
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null)
{
// Unauthorized
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary() { { "controller", "Account" }, { "action", "Login" } });
return;
}
// Get the forms authentication ticket.
var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
Contracts.ISer user = Deserialize(authTicket.UserData); // Up to you to write this Deserialize method -> it should be the reverse of what you did in your Login action
filterContext.HttpContext.User = user;
}
I am using MVC 4 Web Api and I want the users to be authenticated, before using my service.
I have implemented an authorization message handler, that works just fine.
public class AuthorizationHandler : DelegatingHandler
{
private readonly AuthenticationService _authenticationService = new AuthenticationService();
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
IEnumerable<string> apiKeyHeaderValues = null;
if (request.Headers.TryGetValues("X-ApiKey", out apiKeyHeaderValues))
{
var apiKeyHeaderValue = apiKeyHeaderValues.First();
// ... your authentication logic here ...
var user = _authenticationService.GetUserByKey(new Guid(apiKeyHeaderValue));
if (user != null)
{
var userId = user.Id;
var userIdClaim = new Claim(ClaimTypes.SerialNumber, userId.ToString());
var identity = new ClaimsIdentity(new[] { userIdClaim }, "ApiKey");
var principal = new ClaimsPrincipal(identity);
Thread.CurrentPrincipal = principal;
}
}
return base.SendAsync(request, cancellationToken);
}
}
The problem is, that I use forms authentication.
[HttpPost]
public ActionResult Login(UserModel model)
{
if (ModelState.IsValid)
{
var user = _authenticationService.Login(model);
if (user != null)
{
// Add the api key to the HttpResponse???
}
return View(model);
}
return View(model);
}
When I call my api:
[Authorize]
public class TestController : ApiController
{
public string GetLists()
{
return "Weee";
}
}
The handler can not find the X-ApiKey header.
Is there a way to add the user's api key to the http response header and to keep the key there, as long as the user is logged in?
Is there another way to implement this functionality?
I found the following article http://www.asp.net/web-api/overview/working-with-http/http-cookies
Using it I configured my AuthorizationHandler to use cookies:
public class AuthorizationHandler : DelegatingHandler
{
private readonly IAuthenticationService _authenticationService = new AuthenticationService();
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var cookie = request.Headers.GetCookies(Constants.ApiKey).FirstOrDefault();
if (cookie != null)
{
var apiKey = cookie[Constants.ApiKey].Value;
try
{
var guidKey = Guid.Parse(apiKey);
var user = _authenticationService.GetUserByKey(guidKey);
if (user != null)
{
var userIdClaim = new Claim(ClaimTypes.Name, apiKey);
var identity = new ClaimsIdentity(new[] { userIdClaim }, "ApiKey");
var principal = new ClaimsPrincipal(identity);
Thread.CurrentPrincipal = principal;
}
}
catch (FormatException)
{
}
}
return base.SendAsync(request, cancellationToken);
}
}
I configured my Login action result:
[HttpPost]
public ActionResult Login(LoginModel model)
{
if (ModelState.IsValid)
{
var user = _authenticationService.Login(model);
if (user != null)
{
_cookieHelper.SetCookie(user);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "Incorrect username or password");
return View(model);
}
return View(model);
}
Inside it I am using the CookieHelper, that I created. It consists of an interface:
public interface ICookieHelper
{
void SetCookie(User user);
void RemoveCookie();
Guid GetUserId();
}
And a class that implements the interface:
public class CookieHelper : ICookieHelper
{
private readonly HttpContextBase _httpContext;
public CookieHelper(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
public void SetCookie(User user)
{
var cookie = new HttpCookie(Constants.ApiKey, user.UserId.ToString())
{
Expires = DateTime.UtcNow.AddDays(1)
};
_httpContext.Response.Cookies.Add(cookie);
}
public void RemoveCookie()
{
var cookie = _httpContext.Response.Cookies[Constants.ApiKey];
if (cookie != null)
{
cookie.Expires = DateTime.UtcNow.AddDays(-1);
_httpContext.Response.Cookies.Add(cookie);
}
}
public Guid GetUserId()
{
var cookie = _httpContext.Request.Cookies[Constants.ApiKey];
if (cookie != null && cookie.Value != null)
{
return Guid.Parse(cookie.Value);
}
return Guid.Empty;
}
}
By having this configuration, now I can use the Authorize attribute for my ApiControllers:
[Authorize]
public class TestController : ApiController
{
public string Get()
{
return String.Empty;
}
}
This means, that if the user is not logged in. He can not access my api and recieves a 401 error. Also I can retrieve the api key, which I use as a user ID, anywhere in my code, which makes it very clean and readable.
I do not think that using cookies is the best solution, as some user may have disabled them in their browser, but at the moment I have not found a better way to do the authorization.
From your code samples it doesn't seem like you're using Web Forms. Might you be using Forms Authentication? Are you using the Membership Provider inside your service to validate user credentials?
You can use the HttpClient class and maybe its property DefaultRequestHeaders or an HttpRequestMessage from the code that will be calling the API to set the headers.
Here there are some examples of HttpClient:
http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api-from-a-net-client