I've spent a week now securing my Web API, creating custom filters and uses of authentication tokens. My problem now was when I'm requesting in my Web API using POSTMAN and the user was already sign out I can still get values from my API.
How can i manage to force expire my access token? Or is there other way to manage this kind of situation?
NOTE: When I'm requesting using POSTMAN, I copied my access token from the local storage.
Update:
This is what i followed in creating access token. http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api
I tried the same situation as mine to the downloaded solution, still my access token is authenticated
You need to remove the cookies and the session if sign out is not doing that.
FormsAuthentication.SignOut();
Session.Abandon();
// clear authentication cookie
HttpCookie cookie1 = new HttpCookie(FormsAuthentication.FormsCookieName, "");
cookie1.Expires = DateTime.Now.AddYears(-1);
Response.Cookies.Add(cookie1);
// clear session cookie (not necessary for your current problem but i would recommend you do it anyway)
HttpCookie cookie2 = new HttpCookie("ASP.NET_SessionId", "");
cookie2.Expires = DateTime.Now.AddYears(-1);
Response.Cookies.Add(cookie2);
FormsAuthentication.RedirectToLoginPage();
Refrence Taken from here
As per http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api
In this article all the details regarding authorization token is stored on session along with cookies. So you have two ways to sort out this.
Clear all the session and cookies on the logout.
You can also make a custom autorization filter and generate custom access token and store them to local file or database with the timeout limit. On logout you can clear tokens as per the user.
here is an example, how to set custom filters in web api 2.
public class CustomAuthenticateAttribute : Attribute, IAuthenticationFilter
{
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;
AuthenticationHeaderValue authorization = request.Headers.Authorization;
if (authorization == null)
return;
if (authorization.Scheme != "Bearer")
return;
if (String.IsNullOrEmpty(authorization.Parameter))
{
context.ErrorResult = new AuthenticationFailureResult("Missing token", request);
return;
}
TokenL1 tokenL1;
var validateToken = TokenHelper.DecryptToken(authorization.Parameter, out tokenL1);
if (!validateToken)
{
context.ErrorResult = new AuthenticationFailureResult("Token invalid", request);
return;
}
if (!(tokenL1.tokenexpiry > DateTime.Now))
{
context.ErrorResult = new AuthenticationFailureResult("Token expire", request);
return;
}
IPrincipal principal = new GenericPrincipal(new GenericIdentity(tokenL1.email), new string[] { "user" });
if (principal == null)
{
context.ErrorResult = new AuthenticationFailureResult("Invalid token", request);
return;
}
else
{
context.Principal = principal;
}
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
var challenge = new AuthenticationHeaderValue("Bearer");
context.Result = new AddChallengeOnUnauthorizedResult(challenge, context.Result);
return Task.FromResult(0);
}
public bool AllowMultiple
{
get { return false; }
}
}
use this custom filer on the actionresult of a controller like this
[CustomAuthenticate]
public ActionResult Index()
{
return View();
}
Related
I am extremely confused about how the actual authentication works so that [Authorize] does not redirect me to the login page.
Here's my Configuration:
public class IdentityConfig
{
public void Configuration(IAppBuilder app)
{
app.CreatePerOwinContext(() => new MyANTon.DataContext.AntContext());
app.CreatePerOwinContext<UserManager>(UserManager.Create);
app.CreatePerOwinContext<RoleManager<AppRole>>((options, context) =>
new RoleManager<AppRole>(
new RoleStore<AppRole>(context.Get<MyANTon.DataContext.AntContext>())));
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Home/Login"),
});
}
}
In the controller, I want to call an Authenticate(string Email, String Password) method, authenticate the user and return a bool. However, I have no idea how the actual authentication works.
In FormsAuthentication I would create a ticket, what do I do for Identity?
Here's what I have:
public static async System.Threading.Tasks.Task<bool> AuthUserAsync(string Email, string Password)
{
using (var db = new AntContext())
{
string hashedPW = GetHash(Password);
bool userValid = db.Users.Any(user => user.Email == Email && user.Password == hashedPW);
if (userValid)
{
var actUser = db.Users.FirstOrDefault(u => u.Email == Email && u.Password == hashedPW);
if (actUser != null && !actUser.IsLocked)
{
/** What do I do here? **/
}
else if (actUser.IsLocked)
{
LoggingServices.AuthLog(actUser.Email, "Hat versucht auf ein gesperrtes Konto zuzugreifen.");
}
}
return false;
}
}
You are heading in the right direction, what you are doing is sort of using OAuth to faciliate the mapping of your tokens and letting OWin handle the browser information. So, by using the [Authorize] attribute, like you are doing, how are you handling the signing of the identity? Like mentioned above with Forms authentication, you still have to create the Identity/Claim token. In my projects, I do something like this,
protected void IdentitySignin(IUserModel userModel, string providerKey = null, bool isPersistent = true)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userModel.Id.ToString()),
new Claim(ClaimTypes.Name, userModel.UserName),
new Claim("UserContext", userModel.ToString())
};
var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties
{
IsPersistent = isPersistent,
ExpiresUtc = DateTime.UtcNow.AddDays(7)
}, identity);
}
This forces OWIN/OAuth to login the user, and in my web.config I have the following:
<system.webserver>
<authentication mode="None" />
</system.webserver>
And to sign my user out, and force the browser to get a new token:
protected void IdentitySignout()
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie,
DefaultAuthenticationTypes.ExternalCookie);
}
My AuthenticationManager is defined as such:
private IAuthenticationManager AuthenticationManager
{
get { return HttpContext.GetOwinContext().Authentication; }
}
Which is part of Microsoft.OWin.IOwinContext, you will have to add the reference if it does not exist.
You can handle an unauthorized user via the web.config file, or a base-controller, I opted for the base-controller option like so:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (UserContext == null || UserContext.Id.Equals(Guid.Empty))
{
if (filterContext.Controller.GetType() == typeof(AccountController) || filterContext.Controller.GetType() == typeof(HomeController)) return;
filterContext.Result = new RedirectResult("/Home/Index");
return;
}
}
But you can also do it via the AuthorizeAttribute if you wanted. This link will go into great detail on handling Oauth and Asp.Net MVC, may seem daunting at first, but it gives a great layout on using other providers if you decided to incorporate them into a release version.
https://www.codeproject.com/Articles/577384/Introduction-to-OAuth-in-ASP-NET-MVC
When you want Login in to your web-site you send your token to client and you can work with that to request and response in many, in other word, you have to login with your server side.
But when you want to logout from web-site, your client have to know that you want to logout this is not work just for server, the client should do this issue for log out.
I want to suggest you Token-JWT
if you want know about JWT click here
If you want I'll create an Example for you.
Question: What could be causing HttpContext.Current to be null only sometimes?
Question: Is HttpContext.Current initialized after PrincipalService.OnAzureAuthenticationSuccess is called? If so, why only sometiems?
Description
What happens is quite often a user will click sign-in and HttpContext.Current will be null causing the cookie to be never set. Which redirects them back to the home-page and since the cookie wasn't set they click sign-in again and again and again. Sometimes it'll decide to let the cookie be set after 2 or 3 clicks other times it won't do it without clearing cookies or logging out of another Azure AD account (e.g. Our sharepoint server uses Azure AD).
These things seem so odd to me and I haven't been able to pin down what the cause is despite hours of research into it.
Azure Config
public static void ConfigureAzure(IAppBuilder app)
{
// COOKIES: Tells it to use cookies for authentication.
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
// CUSTOMIZE: This is where you would adjust cookie experiation and things of that nature.
SlidingExpiration = true,
ExpireTimeSpan = TimeSpan.FromHours(CookieDurationInHours)
});
//https://azure.microsoft.com/en-us/resources/samples/active-directory-dotnet-webapp-webapi-openidconnect/
// OPEN-ID: Handle OpenID stuff.
var notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = PrincipalService.OnAzureAuthenticationFailure,
// REFERENCE: https://russellyoung.net/2015/09/05/mvc-role-based-authorization-with-azure-active-directory-aad/
AuthorizationCodeReceived = PrincipalService.OnAzureAuthenticationSuccess
};
var options = new OpenIdConnectAuthenticationOptions()
{
ClientId = ClientID,
Authority = Authority,
PostLogoutRedirectUri = PostLogoutRedirectUri,
Notifications = notifications
};
app.UseOpenIdConnectAuthentication(options);
}
On Azure Success
/// <summary>
/// Stores the proper identity cookie (doesn't have customer permissions yet).
/// </summary>
public static Task OnAzureAuthenticationSuccess(AuthorizationCodeReceivedNotification context)
{
var success = false;
var username = context.AuthenticationTicket.Identity.Name;
try
{
success = StoreCookie(username);
}
catch (DbEntityValidationException ex)
{
var errors = ex.EntityValidationErrors.FirstOrDefault()?.ValidationErrors.FirstOrDefault()?.ErrorMessage;
Logger.Log(Level.Error, "An error occurred while storing authentication cookie.", ex);
return Task.FromResult(0);
}
catch (Exception ex)
{
Logger.Log(Level.Error, "An error occurred while storing authentication cookie.", ex);
return Task.FromResult(0);
}
if (success)
{
Logger.Log(Level.Cookie, "Login Complete. Cookie stored successfully. Username: '" + username + "'.");
}
return Task.FromResult(0);
}
Store Cookie
/// <summary>
/// Creates and stores a forms authentication cookie for the user.
/// </summary>
private static bool StoreCookie(string username, bool rememberMe = false)
{
var azureUsers = new AzureUserRepository(new AuthenticationEntities());
var user = azureUsers.Get(u => u.Username == username);
if (user == null)
{
Logger.Log(Level.Cookie, "User '" + username + "' not found.");
throw new NullReferenceException();
}
// Clear any old existing cookies.
if (HttpContext.Current == null)
{
// HERE: This is where it is null (again, only sometimes).
Logger.Log(Level.Debug, "HttpContext is null.");
return false;
}
if (HttpContext.Current.Request == null)
{
Logger.Log(Level.Debug, "HttpContext.Current.Request is null.");
return false;
}
if (HttpContext.Current == null && HttpContext.Current.Response != null)
{
Logger.Log(Level.Debug, "HttpContext.Current.Response is null.");
return false;
}
HttpContext.Current.Request.RemoveFormsAuthCookie();
HttpContext.Current.Response.RemoveFormsAuthCookie();
// Create the principal from the user object.
var principal = new PrincipalModel(user);
// Create and store the cookie in the response.
HttpContext.Current.Response.AddFormsAuthCookie(
username: user.Username,
userData: principal.SerializeUserData(),
isPersistent: true
);
return true;
}
AccountController
[AllowAnonymous]
public void SignIn()
{
if (Request.IsAuthenticated) { return; }
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType
);
Logger.Log(Level.Info, "Sign-In clicked.");
}
public void SignOut()
{
if (!Request.IsAuthenticated) { return; }
// SIGN OUT:
HttpContext.GetOwinContext().Authentication.SignOut(
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType
);
Logger.Log(Level.Info, "Sign-out clicked.");
// COOKIE: Remove the cookie.
var cookie = Request.Cookies[FormsAuthentication.FormsCookieName];
cookie.Expires = DateTime.Now.AddDays(-1); // DateTime.UtcNow.AddDays(-1);
Response.Cookies.Add(cookie);
}
Alright, so it turns out I was being a big dummy and doing things the hard way. I don't 100% fully understand why it was null but I did find a much simpler way around the issue.
Started by removing the code related to me creating my own cookie (i.e. AuthorizationCodeReceived = PrincipalService.OnAzureAuthenticationSuccess).
What happened was I realized that Azure AD was creating its own principal and cookie via the app.UseCookieAuthentication(new CookieAuthenticationOptions()); This realization happened thanks to the git hub project that Fei Xue linked.
After that I switched my custom principal to create based on the 'built-in' cookie instead of the cookie I was creating (which was only getting created half of the time due to HttpContext.Current being null).
Now that the custom principal creation wasn't dependent on HttpContext.Current I didn't have a sign-in loop happening at all because the principal and cookie both exist.
Thanks a ton to Fei Xue!
I want to change the way the default bearer token system works.
I want to login to the webAPI providing the username, password, and mac address of the device. Like so.
Content-Type: application/x-www-form-urlencoded
username=test&password=P#ssword&grant_type=password&client_id=android&device_info=MAC_Address
I then want the API to provide me with a Refresh Token. This token will be valid for say 7 days and will allow for me to get a new access token. However in the refresh token I want to save / embed the security stamp of the users password in the token along with the extirpation date. This way I can check the security stamp when a new access token is requested. (solves password changing scenario)
My access token only needs to store the bare amount of information for it to work. I don't require that the access token store anything specific. I would like to keep it as small as possible. When it expires I will simply request a new access token using my refresh token.
Now I have tried to implement the above but have got my self heavily confused about what to implement where. Here's what i have got.
Step 1: The Startup.Auth.cs
//Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
Provider = new SimpleAuthorizationServerProvider(),
RefreshTokenProvider = new SimpleRefreshTokenProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(20)
};
Now in here I already have some questions. I want to have two providers, one which handles Refresh Tokens and one that handles Access Tokens. Which providers do I need to set? because I see there is also one called AccessTokenProvider = then what is Provider = for?
Step 2: The RereshTokenProvider. This is what I have so far:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
//Used to store all the refresh tokens
public static ConcurrentDictionary<string, AuthenticationTicket> RefreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString("N");
//copy properties and set the desired lifetime of refresh token
var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
{
IssuedUtc = context.Ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddDays(7)
};
//TODO: get mac address from the request headers??
//TODO: save the mac address to db along with user and date
var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);
RefreshTokens.TryAdd(guid, refreshTokenTicket);
context.SetToken(guid);
return Task.FromResult<object>(null);
}
public Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (RefreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
return Task.FromResult<object>(null);
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
}
Now if i understand correctly. The purpose of the SimpleRefreshTokenProvider is to build up a RefreshToken and to the validate it when the api receives a request with one in it?
Step 3: SimpleAuthorizationServerProvider. This is what I have so far. but I have a feeling this is where I have gone wrong. Or im getting confused, What is the purpose of this class? Is it not to validate the AccessToken?
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Not concerned about clients yet
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// validate user credentials
var userManager = context.OwinContext.GetUserManager<FskUserManager>();
FskUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
// create identity
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
//Set properties of the token
// create metadata to pass on to refresh token provider
AuthenticationProperties properties = new AuthenticationProperties(new Dictionary<string, string>
{
{"userName", user.UserName}
});
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
string originalClient;
context.Ticket.Properties.Dictionary.TryGetValue("as:client_id", out originalClient);
var currentClient = context.ClientId;
// chance to change authentication ticket for refresh token requests
var newId = new ClaimsIdentity(context.Ticket.Identity);
newId.AddClaim(new Claim("newClaim", "refreshToken"));
var newTicket = new AuthenticationTicket(newId, context.Ticket.Properties);
context.Validated(newTicket);
}
}
Please what am I missing here?
How to use claims? For example, I want to set access to each page (resource) for each user. I understand, I can do it using roles, but as I understand, claim-based is more effectively. But when I try to create a claim, I see the following method:
userIdentity.AddClaim(new Claim(ClaimTypes.Role, "test role"));
first parameter of constructor of Claim class get ClaimTypes enum, which has many "strange" members like Email, Phone etc. I want to set that this claim and then check this claim to have access to certain resource. I'm on wrong way? How to do it?
From the code above, I am assuming you have already added the claim in startup class on authenticated of your provider as below.
context.Identity.AddClaim(new Claim("urn:google:name", context.Identity.FindFirstValue(ClaimTypes.Name))); // added claim for reading google name
context.Identity.AddClaim(new Claim("urn:google:email", context.Identity.FindFirstValue(ClaimTypes.Email))); // and email too
Once you have added the claims in startup, when the request is actually processed check if its a callback and if yes, read the claims as below(in IHttpHandler).
public void ProcessRequest(HttpContext context)
{
IAuthenticationManager authManager = context.GetOwinContext().Authentication;
if (string.IsNullOrEmpty(context.Request.QueryString[CallBackKey]))
{
string providerName = context.Request.QueryString["provider"] ?? "Google";//I have multiple providers so checking if its google
RedirectToProvider(context, authManager, providerName);
}
else
{
ExternalLoginCallback(context, authManager);
}
}
If its 1st call redirect to provider
private static void RedirectToProvider(HttpContext context, IAuthenticationManager authManager, string providerName)
{
var loginProviders = authManager.GetExternalAuthenticationTypes();
var LoginProvider = loginProviders.Single(x => x.Caption == providerName);
var properties = new AuthenticationProperties()
{
RedirectUri = String.Format("{0}&{1}=true", context.Request.Url, CallBackKey)
};
//string[] authTypes = { LoginProvider.AuthenticationType, DefaultAuthenticationTypes.ExternalCookie };
authManager.Challenge(properties, LoginProvider.AuthenticationType);
//without this it redirect to forms login page
context.Response.SuppressFormsAuthenticationRedirect = true;
}
And finally read the claims you get back
public void ExternalLoginCallback(HttpContext context, IAuthenticationManager authManager)
{
var loginInfo = authManager.GetExternalLoginInfo();
if (loginInfo == null)
{
throw new System.Security.SecurityException("Failed to login");
}
var LoginProvider = loginInfo.Login.LoginProvider;
var ExternalLoginConfirmation = loginInfo.DefaultUserName;
var externalIdentity = authManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var emailClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
var email = emailClaim.Value;
var pictureClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type.Equals("picture"));
var pictureUrl = pictureClaim.Value;
LogInByEmail(context, email, LoginProvider); //redirects to my method of adding claimed user as logged in, you will use yours.
}
Claim doesn't set permission. It's used to verify you that "you are who you claim to be you are". These claims are identified by issuer, usually a 3rd party. See for example this article for description.
So, you should define which claims are necessary (who user should be) in order to access a certain page. Otherwise, using claim-based authorization will be same as using identity based or role based.
I've got a Web API project fronted by Angular, and I want to secure it using a JWT token. I've already got user/pass validation happening, so I think i just need to implement the JWT part.
I believe I've settled on JwtAuthForWebAPI so an example using that would be great.
I assume any method not decorated with [Authorize] will behave as it always does, and that any method decorated with [Authorize] will 401 if the token passed by the client doesn't match.
What I can't yet figure out it how to send the token back to the client upon initial authentication.
I'm trying to just use a magic string to begin, so I have this code:
RegisterRoutes(GlobalConfiguration.Configuration.Routes);
var builder = new SecurityTokenBuilder();
var jwtHandler = new JwtAuthenticationMessageHandler
{
AllowedAudience = "http://xxxx.com",
Issuer = "corp",
SigningToken = builder.CreateFromKey(Convert.ToBase64String(new byte[]{4,2,2,6}))
};
GlobalConfiguration.Configuration.MessageHandlers.Add(jwtHandler);
But I'm not sure how that gets back to the client initially. I think I understand how to handle this on the client, but bonus points if you can also show the Angular side of this interaction.
I ended-up having to take a information from several different places to create a solution that works for me (in reality, the beginnings of a production viable solution - but it works!)
I got rid of JwtAuthForWebAPI (though I did borrow one piece from it to allow requests with no Authorization header to flow through to WebAPI Controller methods not guarded by [Authorize]).
Instead I'm using Microsoft's JWT Library (JSON Web Token Handler for the Microsoft .NET Framework - from NuGet).
In my authentication method, after doing the actual authentication, I create the string version of the token and pass it back along with the authenticated name (the same username passed into me, in this case) and a role which, in reality, would likely be derived during authentication.
Here's the method:
[HttpPost]
public LoginResult PostSignIn([FromBody] Credentials credentials)
{
var auth = new LoginResult() { Authenticated = false };
if (TryLogon(credentials.UserName, credentials.Password))
{
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, credentials.UserName),
new Claim(ClaimTypes.Role, "Admin")
}),
AppliesToAddress = ConfigurationManager.AppSettings["JwtAllowedAudience"],
TokenIssuerName = ConfigurationManager.AppSettings["JwtValidIssuer"],
SigningCredentials = new SigningCredentials(new
InMemorySymmetricSecurityKey(JwtTokenValidationHandler.SymmetricKey),
"http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
"http://www.w3.org/2001/04/xmlenc#sha256")
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
auth.Token = tokenString;
auth.Authenticated = true;
}
return auth;
}
UPDATE
There was a question about handling the token on subsequent requests. What I did was create a DelegatingHandler to try and read/decode the token, then create a Principal and set it into Thread.CurrentPrincipal and HttpContext.Current.User (you need to set it into both). Finally, I decorate the controller methods with the appropriate access restrictions.
Here's the meat of the DelegatingHandler:
private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
{
token = null;
IEnumerable<string> authzHeaders;
if (!request.Headers.TryGetValues("Authorization", out authzHeaders) || authzHeaders.Count() > 1)
{
return false;
}
var bearerToken = authzHeaders.ElementAt(0);
token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
return true;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpStatusCode statusCode;
string token;
var authHeader = request.Headers.Authorization;
if (authHeader == null)
{
// missing authorization header
return base.SendAsync(request, cancellationToken);
}
if (!TryRetrieveToken(request, out token))
{
statusCode = HttpStatusCode.Unauthorized;
return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(statusCode));
}
try
{
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
TokenValidationParameters validationParameters =
new TokenValidationParameters()
{
AllowedAudience = ConfigurationManager.AppSettings["JwtAllowedAudience"],
ValidIssuer = ConfigurationManager.AppSettings["JwtValidIssuer"],
SigningToken = new BinarySecretSecurityToken(SymmetricKey)
};
IPrincipal principal = tokenHandler.ValidateToken(token, validationParameters);
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
return base.SendAsync(request, cancellationToken);
}
catch (SecurityTokenValidationException e)
{
statusCode = HttpStatusCode.Unauthorized;
}
catch (Exception)
{
statusCode = HttpStatusCode.InternalServerError;
}
return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(statusCode));
}
Don't forget to add it into the MessageHandlers pipeline:
public static void Start()
{
GlobalConfiguration.Configuration.MessageHandlers.Add(new JwtTokenValidationHandler());
}
Finally, decorate your controller methods:
[Authorize(Roles = "OneRoleHere")]
[GET("/api/admin/settings/product/allorgs")]
[HttpGet]
public List<Org> GetAllOrganizations()
{
return QueryableDependencies.GetMergedOrganizations().ToList();
}
[Authorize(Roles = "ADifferentRoleHere")]
[GET("/api/admin/settings/product/allorgswithapproval")]
[HttpGet]
public List<ApprovableOrg> GetAllOrganizationsWithApproval()
{
return QueryableDependencies.GetMergedOrganizationsWithApproval().ToList();
}