Handle AuthorizeAttribute answer - c#

I did create custom AuthorizeAttribute that should handle Jwt Bearer token but the question is - now I'm receiving all answers, including 200 and 401 within Ok status, how I should change it in order to receive proper http status code? Here is how AuthorizeAttribute look like:
public class JwtAuthorizeAttribute : AuthorizeAttribute
{
private readonly string role;
public JwtAuthorizeAttribute()
{
}
public JwtAuthorizeAttribute(string role)
{
this.role = role;
}
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var jwtToken = new JwtToken();
string json = String.Empty;
var ctx = actionContext.Request.GetRequestContext();
if (ctx.Principal.Identity.IsAuthenticated) return true;
if (actionContext.Request.Headers.Contains("Authorization"))
{
try
{
IJsonSerializer serializer = new JsonNetSerializer();
IDateTimeProvider provider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, provider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder);
json = decoder.Decode(actionContext.Request.Headers.Authorization.Parameter, SiteGlobal.Secret, verify: true);
jwtToken = JsonConvert.DeserializeObject<JwtToken>(json);
if (jwtToken.aud != SiteGlobal.Audience || jwtToken.iss != SiteGlobal.Issuer || role != jwtToken.role)
{
return false;
}
}
catch (TokenExpiredException)
{
return false;
}
catch (SignatureVerificationException)
{
return false;
}
}
else
{
return false;
}
var identity = new ClaimsIdentity("JWT");
identity.AddClaim(new Claim(ClaimTypes.Name, jwtToken.unique_name));
identity.AddClaim(new Claim(ClaimTypes.Role, jwtToken.role));
identity.AddClaim(new Claim("user_id", jwtToken.user_id.ToString()));
actionContext.Request.GetRequestContext().Principal = new ClaimsPrincipal(identity);
return true;
}
}
This is how controller look like:
[JwtAuthorize("Admin")]
[HttpGet]
[ResponseType(typeof(CatalogueListDto))]
public async Task<IHttpActionResult> Get()
{
var result = await _catalogueService.GetCatalogues();
if (result == null) return BadRequest(ActionAnswer.Failed.CatalogueNotFound);
return Ok(result);
}

i have a similar attribute by following some tutorials in the past. If authorized, i'm not returning true, but i return this:
return base.IsAuthorized(actionContext);
Maybe it's worth a check if this returns you the correct status code.

Related

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

ASP.NET Core - getting a message from AuthenticateResult.Fail

Can not get an error message in HTTP response.
if (!string.IsNullOrWhiteSpace(checkTokenResponse.Error))
{
Logger.LogError(checkTokenResponse.Error);
return AuthenticateResult.Fail(checkTokenResponse.Error);
}
response
I suppose the error message should render into data response field
The same question ASP.NET Core - getting a message back from AuthenticationHandler
Update
code of custom Auth handler
public class AuthHandler : AuthenticationHandler<AuthOptions>
{
private readonly IPrimeApiProxy _primeApiProxy;
private readonly IPrimeProxy _primeProxy;
private readonly int _adminGroupId;
private static readonly string HeaderName = "Authorization";
private static readonly string ApiAuthScheme = "oauth ";
public AuthHandler(
IOptionsMonitor<AuthOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
IPrimeApiProxy primeApiProxy,
IPrimeProxy primeProxy,
ApplicationSettingsProvider settings)
: base(options, logger, encoder, clock)
{
_primeApiProxy = primeApiProxy ?? throw new ArgumentNullException(nameof(primeApiProxy));
_primeProxy = primeProxy ?? throw new ArgumentNullException(nameof(primeProxy));
_adminGroupId = int.Parse((settings as dynamic).AdminGroupId as string);
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
Logger.LogTrace("HandleAuthenticateAsync");
if (!Request.Headers.ContainsKey("Authorization"))
{
return AuthenticateResult.Fail("Missing Authorization Header");
}
try
{
var token = FetchToken(Request);
if (string.IsNullOrWhiteSpace(token.token) && token.userId < 1)
{
Logger.LogError("Invalid token");
return AuthenticateResult.Fail("Invalid token");
}
var checkTokenResponse = await _primeProxy.CheckToken(token.token);
if (!string.IsNullOrWhiteSpace(checkTokenResponse.Error))
{
Logger.LogError(checkTokenResponse.Error);
return AuthenticateResult.Fail(checkTokenResponse.Error);
}
var isValidInt = int.TryParse(checkTokenResponse.UserId, out var userId);
if (!isValidInt)
{
return AuthenticateResult.Fail("User Id is invalid");
}
if (token.userId != userId)
{
return AuthenticateResult.Fail("The token belongs to another user");
}
bool isUserAdminAndCustomGroupMember = false;
if (checkTokenResponse.UserRole == "admin")
{
isUserAdminAndCustomGroupMember = await _primeApiProxy.IsGroupMember(token.token, token.userId, _adminGroupId);
}
var claims = new List<Claim>()
{
new Claim(ClaimTypes.NameIdentifier, token.userId.ToString()),
new Claim(ClaimTypes.Role, isUserAdminAndCustomGroupMember ? "Admin" : "Learner")
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
catch (Exception ex)
{
Logger.LogError(ex, "Auth exception");
return AuthenticateResult.Fail("Invalid Authorization Header");
}
}
private static (string token, int userId) FetchToken(HttpRequest request)
{
string authHeader = request.Headers[HeaderName];
if (authHeader != null && authHeader.StartsWith(ApiAuthScheme, StringComparison.OrdinalIgnoreCase))
{
string token = authHeader.Substring(ApiAuthScheme.Length).Trim();
string[] parts = token.Split(',', StringSplitOptions.RemoveEmptyEntries);
if (int.TryParse(parts[1], out int userId))
{
return (parts[0], userId);
}
}
return (null, 0);
}
}

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!

IdentityServer4 with EF6

I've already implemented the basic Web API protection via IdentityServer4 based on this.
The demo is based on in-memory data. And most of tutorials are based on EF Core implementation for user data. As I searched there was a IUserService in IdentityServer3 which is now missing in version 4.
builder.AddInMemoryClients(Clients.Get());
builder.AddInMemoryScopes(Scopes.Get());
builder.AddInMemoryUsers(Users.Get());
How can I retrieve my user data from an EF6 store?
In Startup.cs, do this
builder.Services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
builder.Services.AddTransient<IProfileService, ProfileService>();
Here is a sample of ResourceOwnerPasswordValidator and ProfileService
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
private MyUserManager _myUserService { get; set; }
public ResourceOwnerPasswordValidator()
{
_myUserService = new MyUserManager();
}
public async Task<CustomGrantValidationResult> ValidateAsync(string userName, string password, ValidatedTokenRequest request)
{
var user = await _myUserService.FindByNameAsync(userName);
if (user != null && await _myUserService.CheckPasswordAsync(user, password))
{
return new CustomGrantValidationResult(user.EmailAddress, "password");
}
return new CustomGrantValidationResult("Invalid username or password");
}
}
public class ProfileService : IProfileService
{
MyUserManager _myUserManager;
public ProfileService()
{
_myUserManager = new MyUserManager();
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.FindFirst("sub")?.Value;
if (sub != null)
{
var user = await _myUserManager.FindByIdAsync(sub);
var cp = await getClaims(user);
var claims = cp.Claims;
if (context.AllClaimsRequested == false ||
(context.RequestedClaimTypes != null && context.RequestedClaimTypes.Any()))
{
claims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToArray().AsEnumerable();
}
context.IssuedClaims = claims;
}
}
public Task IsActiveAsync(IsActiveContext context)
{
return Task.FromResult(0);
}
private async Task<ClaimsPrincipal> getClaims(CustomerSite user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var userId = await _myUserManager.GetUserIdAsync(user);
var userName = await _myUserManager.GetUserNameAsync(user);
var id = new ClaimsIdentity();
id.AddClaim(new Claim(JwtClaimTypes.Id, userId));
id.AddClaim(new Claim(JwtClaimTypes.PreferredUserName, userName));
var roles = await _myUserManager.GetRolesAsync(user);
foreach (var roleName in roles)
{
id.AddClaim(new Claim(JwtClaimTypes.Role, roleName));
}
id.AddClaims(await _myUserManager.GetClaimsAsync(user));
return new ClaimsPrincipal(id);
}
}

authorize error from authorize handler class

I cannot seem to login when I call -- api/values. the client-end throws "Authorization has been denied for this request." message.
I tried debugging the basicAuthHandler class but it does not seem to be crashing anywhere, so I am little stuck and how can I pin point the issue.
could it be my validate method or constructor in my global.aspx?
public class BasicAuthMessageHandler : DelegatingHandler
{
private const string BasicAuthResponseHeader = "WWW-Authenticate";
private const string BasicAuthResponseHeaderValue = "Basic";
//[Inject]
//public iUser Repository { get; set; }
// private readonly iUser Repository;
private readonly iUser Repository = new User();
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
AuthenticationHeaderValue authValue = request.Headers.Authorization;
if (authValue != null && !String.IsNullOrWhiteSpace(authValue.Parameter))
{
api_login parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
if (parsedCredentials != null)
{
IPrincipal principal;
if (TryGetPrincipal(parsedCredentials.username, parsedCredentials.password, out principal))
{
Thread.CurrentPrincipal = principal;
//request.GetRequestContext().Principal = principal;
}
}
}
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
if (response.StatusCode == HttpStatusCode.Unauthorized && !response.Headers.Contains(BasicAuthResponseHeader))
{
response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
}
return response;
});
}
private api_login 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 api_login()
{
username = credentials[0],
password = credentials[1],
};
}
private bool TryGetPrincipal(string userName, string password, out IPrincipal principal)
{
// this is the method that authenticates against my repository (in this case, hard coded)
// you can replace this with whatever logic you'd use, but proper separation would put the
// data access in a repository or separate layer/library.
api_login user = Repository.Validate2(userName, password);
if (user.username != null)
{
// once the user is verified, assign it to an IPrincipal with the identity name and applicable roles
principal = new GenericPrincipal(new GenericIdentity(user.username), null);
}
principal = null;
return false;
}
}
}
global.aspx:
GlobalConfiguration.Configuration.MessageHandlers.Add(new BasicAuthMessageHandler());
Any help would be very much appreciated.
Thank you.
I think you didn't handle the response correctly in your code, I created a MessageHandler for Basic Authentication base on your code, hope it'll give you an good idea (I didn't test it), see below:
public class BasicAuthMessageHandler : DelegatingHandler
{
private const string BasicAuthResponseHeader = "WWW-Authenticate";
private const string BasicAuthResponseHeaderValue = "Basic";
//[Inject]
//public iUser Repository { get; set; }
// private readonly iUser Repository;
private readonly iUser Repository = new User();
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
AuthenticationHeaderValue authValue = request.Headers.Authorization;
if (authValue == null || authValue.Scheme != BasicAuthResponseHeaderValue)
{
return Unauthorized(request);
}
string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(authValue.Parameter)).Split(new[] { ':' });
if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[1]))
{
return Unauthorized(request);
}
api_login user = Repository.Validate2(credentials[0], credentials[1]);
if (user == null)
{
return Unauthorized(request);
}
IPrincipal principal = new GenericPrincipal(new GenericIdentity(user.username, BasicAuthResponseHeaderValue), null);
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
return base.SendAsync(request, cancellationToken);
}
private Task<HttpResponseMessage> Unauthorized(HttpRequestMessage request)
{
var response = request.CreateResponse(HttpStatusCode.Unauthorized);
response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
var task = new TaskCompletionSource<HttpResponseMessage>();
task.SetResult(response);
return task.Task;
}
}

Categories

Resources