Jwt authentication, authorization any request - c#

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);
}
}

Related

How to move custom authorization attribute policy implementation logic to startup?

I have implemented a custom [Authorize] attribute in a ASP.NET MVC Core 3.1 app. The main reason I have a custom one is because the app uses a lot of AJAX and I couldn't figure out how to get it to work with AJAX. The attribute also implements ActionFilterAttribute instead of IAuthorizationFilter due to issues I posted about here. Having this custom attribute means implementing my own logic to handle roles, which I did.
I've also implemented code to handle policies that group roles, but the code is inside the attribute itself. When a developer would think of changing policies within MVC framework, they'd probably think to go to Startup.cs.
How I could put the logic for policies in Startup.cs and have my custom attribute use it?
Custom attribute:
public class AuthorizeUser : ActionFilterAttribute
{
public Policies Policy { get; set; }
public AuthorizeUser(Policies policy)
{
Policy = policy;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
string signInPageUrl = "/UserAccess/Index";
string notAuthorizedUrl = "/UserAccess/NotAuthorized";
if (context.HttpContext.User.Identity.IsAuthenticated)
{
List<string> roles = GetRolesByPolicy(Policy);
bool userHasRole = false;
foreach (var role in roles)
{
if (context.HttpContext.User.IsInRole(role.ToUpper()))
{
userHasRole = true;
}
}
if (userHasRole == false)
{
if (context.HttpContext.Request.IsAjaxRequest())
{
context.HttpContext.Response.StatusCode = 401;
JsonResult jsonResult = new JsonResult(new { redirectUrl = notAuthorizedUrl });
context.Result = jsonResult;
}
else
{
context.Result = new RedirectResult(notAuthorizedUrl);
}
}
}
else
{
if (context.HttpContext.Request.IsAjaxRequest())
{
context.HttpContext.Response.StatusCode = 403;
JsonResult jsonResult = new JsonResult(new { redirectUrl = signInPageUrl });
context.Result = jsonResult;
}
else
{
context.Result = new RedirectResult(signInPageUrl);
}
}
}
private List<string> GetRolesByPolicy(Policies policy)
{
List<RoleModel.Roles> roleEnums = new List<RoleModel.Roles>();
roleEnums.Add(RoleModel.Roles.Admin); //admin is always allowed all actions
switch (policy)
{
case Policies.EditUsers:
roleEnums.AddRange(new List<RoleModel.Roles>
{
RoleModel.Roles.User,
RoleModel.Roles.Customer,
RoleModel.Roles.Developer,
RoleModel.Roles.Manager
});
break;
case Policies.ManageOrders:
roleEnums.AddRange(new List<RoleModel.Roles>
{
RoleModel.Roles.User,
RoleModel.Roles.Customer
});
break;
case Policies.SendOrders:
roleEnums.AddRange(new List<RoleModel.Roles>
{
RoleModel.Roles.User
});
break;
}
return roleEnums.ConvertAll(r => r.ToString());
}
}
Policies enum
public enum Policies
{
EditUsers,
ManageOrders,
SendOrders
}

Role Based Authentication for web API not working

I'm facing an issue while working with Role-Based authentication for web APi.
I have a controller class where the controller has a custom authorize attribute called Myauthorize.
I have a method inside the controller which can be accessed only with Admin access.
But the same method has been calling with QA access as well.
Could anyone please help with the below?
Please find the code below.
Controller :
namespace Hosiptal.Controllers.office
{
[MyAuthorize(Constants.Roles.Admin)]
public class UserRolesController : ApiController
{
private readonly IRepository<EntityModels.Role> rolesRepository;
public UserRolesController(IRepository<EntityModels.Role> rolesRepository)
{
this.rolesRepository = rolesRepository;
}
// GET: Users
[HttpGet]
[Route("")]
public IEnumerable<Role> GetAll()
{
return this.rolesRepository.GetAll()
.ToArray()
.Select(r => Mapper.Current.Get<Role>(r));
}
}
}
MyAuthorize has followed.
namespace Hospital.Web.Filters.WebApi
{
public class MyAuthorize: AuthorizeAttribute
{
private readonly string[] allowedroles;
private static IUserProfileRepository UserProfileRepository
{
get { return IoC.Current.Resolve<IUserProfileRepository>(); }
}
public MyAuthorize(params string[] roles)
{
this.allowedroles = roles;
}
public override Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken
cancellationToken)
{
var claimsIdentity = actionContext.RequestContext.Principal.Identity as ClaimsIdentity;
var alias = claimsIdentity.Name.Split('#')[0];
if (alias == null)
{
throw new ArgumentNullException(nameof(actionContext));
}
user(alias);
return base.OnAuthorizationAsync(actionContext, cancellationToken);
}
public static GenericPrincipal user(string userName)
{
userName = userName.ToUpper();
var userProfile = UserProfileRepository.Get(userName) ?? new UserProfile()
{
UserName = userName,
Roles = new List<Role>(),
FirstLoginDateUtc = DateTime.UtcNow
};
return CreatePrincipal(userProfile);
}
public static GenericPrincipal CreatePrincipal(UserProfile user)
{
var identity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name,
user.UserName) }, "Custom");
return new GenericPrincipal(identity, user.Roles.Select(i =>
i.Name).ToArray());
}
}
}
How can restrict the user here based on access level?
If you review the source code for the AuthorizeAttribute class, you will see that it uses the controller context request's principal to perform authorization, so override the IsAuthorized method instead, move your code there and assign the principal you create to the context request's principal:
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var claimsIdentity = actionContext.RequestContext.Principal.Identity as ClaimsIdentity;
var alias = claimsIdentity.Name.Split('#')[0];
if (alias == null)
{
throw new ArgumentNullException(nameof(actionContext));
}
//This sets the context's principal so the base class code can validate
actionContext.ControllerContext.RequestContext.Principal = user(alias);
//Call the base class and let it work its magic
return base.IsAuthorized(actionContext);
}
I will refrain from commenting on the design itself. This should fix your issue.
This is what's working for me
public class AdminAuthorizeAttributee : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (AuthorizeRequest(actionContext))
{
return;
}
HandleUnauthorizedRequest(actionContext);
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
base.HandleUnauthorizedRequest(actionContext);
}
private bool AuthorizeRequest(HttpActionContext actionContext)
{
try
{
var username = HttpContext.Current.User.Identity.Name;
var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
var user = userManager.Users.Where(a => a.UserName == username).FirstOrDefault();
var rolesForUser = userManager.GetRoles(user.Id);
var role = "Admin";
if (rolesForUser.Contains(role))
{
return true;
}
return false;
}
catch (Exception ex)
{
return false;
}
}
}
and in Controller
[AdminAuthorizeAttributee]
public class YOUR_Controller : ApiController
You don't need to create your own authorize filter for this.
Use the built-in [Authorize(Roles = "Admin")] - which will check if the user has a claim called "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" and if that value matches to the one you put in that authorize attribute, the authorization will succeed.
So in your case just make sure, when you log in the user to set his claim with the role like this:
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Role, "Admin"), //here set the users role
// ... other claims
};
(ClaimTypes class is from the namespace System.Security.Claims)
And then the [Authorize(Roles = "Admin")] should work just fine

Implementing [Authorize(Roles = "something")] with JWT in asp.net

I'm new to JWT and authorization in general. In our.NET 4.7.2 web application, we have an ApplicationPrincipal.cs that has a constructor that takes two arguments: IPrincipal object and UserAccount object. We'd use this in our JWT token validation's SetPrincipalAsync method. Up until now, we've always being passing a useId in JWT payload in order to create a UserAccount object off of it. But, now we have an api controller that we're making use of Authorize attribute with a Role (let say "randomName" that's encoded in JWT payload) and we're not asking for a userId in JWT payload. I can have a second constructor in my ApplicationPrincipal class to only accept a IPrincipal object in the case where I'm authorizing a request without userId, but then the Identity would be null.
I'm able to successfully validate the JWT token and return a claimsPrincipal object; But, when I test my api using Postman it returns 401 - Not Authorized.
public class ApplicationIdentity : IIdentity
{
public ApplicationIdentity(UserAccount account)
{
Name = account.FullName;
Account = account;
}
public UserAccount Account { get; }
public string AuthenticationType => "JWT";
public bool IsAuthenticated => true;
public string Name { get; }
}
public class ApplicationPrincipal : IPrincipal
{
private readonly IPrincipal _principal;
public IIdentity Identity { get; }
public ApplicationPrincipal(IPrincipal principal, UserAccount account)
{
_principal = principal;
Identity = new ApplicationIdentity(account);
}
public ApplicationPrincipal(IPrincipal principal)
{
_principal = principal;
}
public bool IsInRole(string role) => _principal.IsInRole(role);
}
public class TokenValidationHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken
)
{
try
{
var (principal, jwtSecurityToken) = await ValidateJwtAsync(token).ConfigureAwait(true);
var payload = ValidatePayload(jwtSecurityToken);
await SetPrincipalAsync(principal, payload).ConfigureAwait(true);
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
catch (SecurityTokenValidationException ex)
{
return request.CreateApiErrorResponse(HttpStatusCode.Unauthorized, ex);
}
catch (Exception ex)
{
return request.CreateApiErrorResponse(HttpStatusCode.InternalServerError, ex);
}
}
private static async Task SetPrincipalAsync(IPrincipal principal, JWTPayload payload)
{
if (!Guid.TryParse(payload.UserId, out var userId) && payload.api?.version != "someName")
{
throw new SecurityTokenValidationException("Token does not have valid user ID.");
}
if (payload.api?.version == "someName")
{
var myPrincipal = new ApplicationPrincipal(principal);
HttpContext.Current.User = myPrincipal;
}
else
{
var myPrincipal = new ApplicationPrincipal(principal);
var handler = new Account(userId, comeOtherValue);
var account = await CacheManager.Instance.GetOrAddAsync(handler).ConfigureAwait(true);
if (account == null)
{
throw new SecurityTokenValidationException("Could not find user account.");
}
myPrincipal = new ApplicationPrincipal(principal, account);
HttpContext.Current.User = myPrincipal;
}
}
private static async Task<(IPrincipal Principal, JwtSecurityToken Token)> ValidateJwtAsync(string token, string requestingApi)
{
// the rest of the code
ClaimsPrincipal claimsPrincipal;
SecurityToken securityToken;
var handler = new JwtSecurityTokenHandler();
try
{
claimsPrincipal = handler.ValidateToken(
token,
validationParameters,
out securityToken
);
if (requestingApi.Contains("the specific api with Role"))
{
var ci = new ClaimsIdentity();
ci.AddClaim(new Claim(ClaimTypes.Role, "roleName")); //role name applied on the api
claimsPrincipal.AddIdentity(ci);
}
}
catch (ArgumentException ex)
{
// some code
}
var jwtToken = (JwtSecurityToken)securityToken;
if (jwtToken == null)
{
//some code
}
return (claimsPrincipal, jwtToken);
}
}
My goal is to apply [Authorize(Roles = "randomName")] to the controller based on the JWT payload which has a specific nested property:
{"http://clients": {"api" : {"version1" : "randomName"}}
Any advice would be appreciated!

Basic Token authentification and authorization on Web.Api

So I have an MVC Application that calls WebApi method.
My Authorization on MVC App is done like this
public class CustomAuthorizeAttribute : AuthorizeAttribute {
private RolesEnum _role;
public CustomAuthorizeAttribute() {
_role = RolesEnum.User;
}
public CustomAuthorizeAttribute(RolesEnum role) {
_role = role;
}
protected override bool AuthorizeCore(HttpContextBase httpContext) {
User currentUser = (User)httpContext.Session["CurrentUser"];
if (currentUser == null) {
return false;
}
if (currentUser.Role == RolesEnum.User && _role == RolesEnum.Admin) {
return false;
}
return true;
}
The authentification is done calling a WebApi method
[HttpPost]
public ActionResult Login(string username, string password)
{
User acc = new User();
acc.Username = username;
acc.Password = password;
acc = accBL.Login(acc);
if (acc != null) {
Session.Add("CurrentUser", acc);
return RedirectToAction("Index", "Project", null);
} else {
return View();
}
}
Login method looks like this
public User LogIn(User acc) {
try {
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(BASE_URL);
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.PostAsJsonAsync("api/Account/Login", acc).Result;
if (response.IsSuccessStatusCode) {
return response.Content.ReadAsAsync<User>().Result;
} else {
return null;
}
} catch {
return null;
}
}
And WebApi method looks like this
[Route("api/Account/Login")]
[HttpPost]
public IHttpActionResult Login(User userModel) {
User user = db.Users.Where(p => p.Username == userModel.Username && p.Password == userModel.Password).FirstOrDefault();
if (user != null) {
return Ok(user);
} else {
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
}
How can I make a connection between the MVC App and the WebApi Services. My authorization and authentification works on MVC part, but my WebApi Services can be called without this any authorization/authenification. How can I secure my WebApi too based on my example? I've been working for about 3 weeks with MVC and WebApi and many things are not very clear too me.
Should I just create a GUID in public IHttpActionResult Login(User userModel) and check for it everytime i a method is called? How do i pass this GUID to MVC App and from MVC to WebApi?
What you can do is create some sort of a token (eg. JWT) in the WebAPI Login() method and return with the Ok() response (to the MVC app). Users calling your API endpoints have to send this token back (eg. in a custom "Token" header). You can validate the token inside a custom WebAPI authorize attribute that you use in your API endpoints.
eg.
Login endpoint
[Route("api/Account/Login")]
[HttpPost]
public object Login(User userModel) {
User user = ...;
string token = CreateTokenForUser(user);
if (user != null) {
// return user and token back
return new {User = user, Token = token};
} else {
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
}
Custom authentication filter
public class UserAuthAttribute : ActionFilterAttribute, IAuthenticationFilter
{
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
string token = null;
IEnumerable<string> tokenHeader;
if (context.Request.Headers.TryGetValues("Token", out tokenHeader))
token = tokenHeader.FirstOrDefault();
if (token != null && IsTokenValid(token)
{
// user has been authenticated i.e. send us a token we sent back earlier
}
else
{
// set ErrorResult - this will result in sending a 401 Unauthorized
context.ErrorResult = new AuthenticationFailureResult(Invalid token", context.Request);
}
}
}
Other endpoint which only authenticated users should be allowed access
[Route("api/Values")]
[HttpGet]
[UserAuth]
public object GetValues() {
// only requests with a valid token will be able to call this because of the [UserAuth] attribute
}

How to use Api key in Web Api for service authentication using forms authentication

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

Categories

Resources