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!
Related
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
I create project for test authentication in ASP.Net Core Web API with using JWT tokens. I implemented the basic functionality for working with accounts, but I ran into some problems.
UsersController:
[Authorize]
[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly IAuthenticationService _authenticationService;
public UsersController(
IUserService userService,
IAuthenticationService authenticationService)
{
_userService = userService;
_authenticationService = authenticationService;
}
// PUT: users/5
[HttpPut("{id}")]
public async Task<ActionResult> PutUser(int id, [FromBody]UpdateUserModel model)
{
try
{
var user = await _userService.UpdateAsync(model, id);
return Ok();
}
catch(Exception ex)
{
return BadRequest(new { message = ex.Message });
}
}
// POST : users/authenticate
[AllowAnonymous]
[HttpPost("authenticate")]
public async Task<ActionResult<User>> Authenticate([FromBody] AuthenticateUserModel model)
{
var user = await _authenticationService.AuthenticateAsync(model);
if (user == null)
return BadRequest(new { message = "Login or password is incorrect" });
return Ok(user);
}
}
AuthenticationService:
public async Task<User> AuthenticateAsync(AuthenticateUserModel model)
{
var users = await _context.Users.ToListAsync();
var user = users.SingleOrDefault(x => x.Login == model.Login && x.Password == model.Password);
if (user == null)
return null;
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString()),
new Claim(ClaimTypes.Role, user.Role)
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
user.Token = tokenHandler.WriteToken(token);
return user.WithoutPassword();
}
It turns out that after authorization, any user can edit the data of another user if we specify a different id in the client who will send requests. Is it possible to somehow limit the actions thanks to the token or how is it better to do this?
You should't trust the submitted data from the user. you should set UserId in payload data like what you did yourself
new Claim(ClaimTypes.Name, user.Id.ToString()),
and when user edit the data get user id from JWT like this
public int GetCurrentUserId()
{
var claimsIdentity = _contextAccessor.HttpContext.User.Identity as ClaimsIdentity;
var userDataClaim = claimsIdentity?.FindFirst(ClaimTypes.Name);
var userId = userDataClaim?.Value;
return string.IsNullOrWhiteSpace(userId) ? 0 : int.Parse(userId);
}
or
int userId = Convert.ToInt32((User.Identity as ClaimsIdentity).FindFirst(ClaimTypes.Name).Value);
and finally
[HttpPut("PutUser")]
public async Task<ActionResult> PutUser([FromBody]UpdateUserModel model)
{
try
{
int userId = Convert.ToInt32((User.Identity as ClaimsIdentity).FindFirst(ClaimTypes.Name).Value);
var user = await _userService.UpdateAsync(model, userId);
return Ok();
}
catch (Exception ex)
{
return BadRequest(new { message = ex.Message });
}
}
In the out of the box ASP.NET WEB API oAuth implementation after a new user calls:
GET api/Account/ExternalLogins?returnUrl=%2F&generateState=true
user is redirected to external log in (in my case Facebook) resulting in a token that they use for registration (out of the box code bellow)
// POST api/Account/RegisterExternal
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal([FromBody]RegisterExternalBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
IdentityUser user = new IdentityUser
{
UserName = model.UserName
};
user.Logins.Add(new IdentityUserLogin
{
LoginProvider = externalLogin.LoginProvider,
ProviderKey = externalLogin.ProviderKey
});
IdentityResult result = await UserManager.CreateAsync(user);
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
During RegisterExternal I want to populate another database using the data on their Facebook (first name, last name, email, friends, ext..)
The Bearer token I am getting during registration can not simply be called as such:
var accessToken = "token from header";
var client = new FacebookClient(accessToken);
So from what I understand I need to modify Startup.Auth with claims for this data as i have done by adding:
var facebookProvider = new FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
// Add the email id to the claim
context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email));
return Task.FromResult(0);
}
};
var options = new FacebookAuthenticationOptions()
{
AppId = "xxxxxxxxxxxxxxxxx",
AppSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
Provider = facebookProvider
};
options.Scope.Add("email");
options.Scope.Add("user_friends");
options.Scope.Add("public_profile");
app.UseFacebookAuthentication(options);
But then how do I go about fetching that data in my RegisterExternal method?
I had the same problem (I think) - the problem was that FB the OAuth infrastructure was only filling the basic data and I wanted a bit more.
After doing some digging into the source code of ASP.NET identity, I turned out with the following:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions
{
AppId = "",
AppSecret = "",
Scope = { "public_profile", "email", "user_birthday", "user_location" },
Provider = new FacebookAuthProvider(),
UserInformationEndpoint = "https://graph.facebook.com/v2.5/me?fields=id,name,email,first_name,last_name,location,birthday,picture",
});
and the important part here is the custom provider:
private class FacebookAuthProvider : FacebookAuthenticationProvider
{
/// <summary>
/// Invoked whenever Facebook succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="T:System.Security.Claims.ClaimsIdentity" />.</param>
/// <returns>A <see cref="T:System.Threading.Tasks.Task" /> representing the completed operation.</returns>
public override Task Authenticated(FacebookAuthenticatedContext context)
{
TryParseProperty(context, "first_name", Claims.FirstName);
TryParseProperty(context, "last_name", Claims.LastName);
TryParseProperty(context, "picture.data.url", Claims.PhotoUrl);
return base.Authenticated(context);
}
private void TryParseProperty(FacebookAuthenticatedContext context, string name, string targetName)
{
var value = context.User.SelectToken(name);
if (value != null)
{
context.Identity.AddClaim(targetName, value.ToString());
}
}
}
This basically puts all the data in the claim and can be retrieved anywhere else the same way.
The external provider, in this case Facebook, will populate the Claims and these can be accessed in your callback method in LoginInfo.
Here's the code for reading the Facebook Access token:
var accessToken = loginInfo.ExternalIdentity.Claims.FirstOrDefault(x => x.Type == Constants.FacebookAccessToken).Value;
If you set a breakpoint there you'll be able to see what else is returned by Facebook.
John Mc really pointed me in the right direction, here is a more full solution.
// POST api/Account/RegisterExternalToken
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("RegisterExternalToken")]
public async Task<IHttpActionResult> RegisterExternalToken()
{
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
var facebookToken = externalLogin.Token;
And then in the claims (this is the key part) as John's pointed out:
private class ExternalLoginData
{
public string LoginProvider { get; set; }
public string ProviderKey { get; set; }
public string UserName { get; set; }
public string Token { get; set; }
public IList<Claim> GetClaims()
{
IList<Claim> claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, ProviderKey, null, LoginProvider));
if (UserName != null)
{
claims.Add(new Claim(ClaimTypes.Name, UserName, null, LoginProvider));
}
if (Token != null)
{
claims.Add(new Claim("FacebookAccessToken", Token, null, LoginProvider));
}
return claims;
}
public static ExternalLoginData FromIdentity(ClaimsIdentity identity)
{
if (identity == null)
{
return null;
}
Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);
if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer)
|| String.IsNullOrEmpty(providerKeyClaim.Value))
{
return null;
}
if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
{
return null;
}
return new ExternalLoginData
{
LoginProvider = providerKeyClaim.Issuer,
ProviderKey = providerKeyClaim.Value,
UserName = identity.FindFirstValue(ClaimTypes.Name),
Token = identity.Claims.FirstOrDefault(x => x.Type.Contains("FacebookAccessToken")).Value
};
}
}
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;
}
}
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