I have a custom attribute where I manually wanna check if a claims token is valid. How do I do that?
public class AuthorizeClaimsAttribute : AuthorizeAttribute {
protected override bool UserAuthorized(IPrincipal user) {
var cookie = HttpContext.Current.Request.Cookies.Get("bearerToken");
if (cookie != null) {
//Check if token is valid, how?
}
return false;
}
}
The token is created as follow:
var identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
identity.AddClaim(new Claim("Username", model.Username));
identity.AddClaim(new Claim("IsAdmin", isAdmin.ToString()));
var properties = new AuthenticationProperties() {
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.Add(Startup.OAuthOptions.AccessTokenExpireTimeSpan)
};
var ticket = new AuthenticationTicket(identity, properties);
var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
Note: I cannot use the existing Authorize attribute. That´s why I need to check it manually.
One method is to store the token alongside its username somewhere in a persistant data structure
For example, when you create the Identity store the model.UserName & the accessToken in a a database of your choice.
Then, when you want to check your cookie you can re-open your database and query for it and take the appropriate action.
Also, adding that date in the database will also help you keep the size of it down resulting in faster searches, i.e. if your token only lasts for 3 months, delete the old ones as part of maintenance
Related
In my project, I am using token-based authentication and after a successful login, I store some user-specific values in user's token and to do this I have used Claims.
Below is the code I am using to store claims after login:
User user = new UserManager().GetUser(UserName, Password);
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, user.FullName),
new Claim(ClaimTypes.Email, user.Email),
new Claim("IsLocked", Convert.ToString(user.IsLocked))
};
AuthenticationProperties properties = CreateProperties(context.UserName);
ClaimsIdentity oAuthIdentity = new ClaimsIdentity(claims, Startup.OAuthOptions.AuthenticationType);
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
As you can see in the above code that I have a claim to store IsLocked value of the user. As per the requirement, I need to prevent access of each API action from users whose account is locked. To do so, I have created a custom action filter and inside that, I use the value of IsLocked claim and thus prevent actions from being executed if user's claim value says that the user account is locked.
Below is the code of my custom action filter:
public class AllowActiveUsersAttribute : ActionFilterAttribute
{
public AllowActiveUsersAttribute()
{
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
var identity = (ClaimsPrincipal)Thread.CurrentPrincipal;
if (Convert.ToBoolean(identity.Claims.Where(c => c.Type == "IsLocked").Select(c => c.Value).SingleOrDefault()))
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
And then I use this custom Attribute on all Web API actions, like:
[AllowActiveUsers]
public async Task<IHttpActionResult> GetAccountDetails()
This code works perfectly and I get Unauthorized error when I log in with a locked account and then try to use any API endpoint.
In our system, we have some rules and breaking those rules can lock the users' account. When the account gets locked then the user shouldn't be able to access any API endpoint. So after the successful login (with an account that is not locked), if a user breaks any rule then his/her account should get locked immediately and after that he/she must not be able to use any API endpoints.
To do this, I added code to update the value of IsLocked claim and it successfully updates the claim value. But when I try to get the value of IsLocked claim in the custom action then I get the same old value instead of the new return value. Below is the code that I am using to update the claim value.
// check for the existing claim and remove it
var user = User as ClaimsPrincipal;
var identity = user.Identity as ClaimsIdentity;
var claim = (from c in user.Claims where c.Type == "IsLocked" select c).FirstOrDefault();
if (claim != null)
identity.RemoveClaim(claim);
// add new claim
identity.AddClaim(new Claim("IsLocked", Convert.ToString(true)));
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = true });
Can you please suggest how I can get the new value in the custom attribute so that if a user's account gets locked then from next requests, none of the API requests should be entertained?
That's cause you are still using the old/existing token which still don't have the updated value of IsLocked and thus the scenario. To resolve this, either the user has to start afresh by means of generating a new token which would have the updated value. Essentially a new access token has to be generated.
See this issue link for more understanding https://github.com/IdentityServer/IdentityServer3/issues/2783
I'm looking for a minimal example for a custom authentication writen in C# for asp.net core 2 based on for example API keys.
Mircosoft has a pretty good documentation about doing this with cookies, however this is not what I want. Since I want to use API keys (given by http-header, GET or Cookie, ...) I never make a call to HttpContext.SignInAsync and this is maybe the issue I can't find/google my way around.
I built an simple AuthenticationHandler (based on this - since I read that custom middlewares are not the way to go anymore) which looks something like this:
internal class CustomAuthHandler : AuthenticationHandler<CustomAuthOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// parse cookies to find APIKEY
if(Context.Request.Cookies.ContainsKey("APIKEY"))
{
string APIKEY = Request.Cookies["APIKEY"];
// ... checking DB for APIKEY ...
// creating claims
var claims = new[]
{
new Claim( /* ... */ ),
// ...
};
var claimsIdentity = new ClaimsIdentity(claims);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
var ticket = new AuthenticationTicket(claimsPrincipal, new AuthenticationProperties(), "Custom Scheme");
return AuthenticateResult.Success(ticket); // this line gets called
}
return AuthenticateResult.NoResult();
}
}
But when I have an API endpoint with just the [Authorize] attribute the DenyAnonymousAuthorizationRequirement denies the request cause the user is not allowed (cause IsAuthenticated == false which is readonly, all claims are shown properly)
Change var claimsIdentity = new ClaimsIdentity(claims); into something like var claimsIdentity = new ClaimsIdentity(claims, "Password"); (of course, instead of "Password" use the AuthenticationType that best fits your case).
Similar question here: Why is my ClaimsIdentity IsAuthenticated always false (for web api Authorize filter)?
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'm using ASP.NET MVC 5 project with identity 2.1.0 and VS2013 U4. I want to add claims to user during registration in order to be stored in db. These claims represent user custom properties.
As I created a web page for administrator to create/edit/delete users, I'm still using create method from AccountController to create a user, but I don't want to login that user. How can I add those claims to the user ?
You probably already have a UserManager class. You can use that one to create users and to add claims.
As an example in a controller:
// gather some context stuff
var context = this.Request.GetContext();
// gather the user manager
var usermanager = context.Get<ApplicationUserManager>();
// add a country claim (given you have the userId)
usermanager.AddClaim("userid", new Claim(ClaimTypes.Country, "Germany"));
In order for this to work you need to implement your own UserManager and link it with the OWIN context (in the example it's ApplicationUserManager which basically is class ApplicationUserManager : UserManager<ApplicationUser> { } with only a small amount of configuration added). A bit of reading is available here: https://msdn.microsoft.com/en-us/library/dn613290%28v=vs.108%29.aspx
you can use Like
private void SignInAsync(User User)
{
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, User.Employee.Name));
claims.Add(new Claim(ClaimTypes.Email, User.Employee.EmailId));
claims.Add(new Claim(ClaimTypes.Role, User.RoleId.ToString()));
var id = new ClaimsIdentity(claims,
DefaultAuthenticationTypes.ApplicationCookie);
var claimsPrincipal = new ClaimsPrincipal(id);
// Set current principal
Thread.CurrentPrincipal = claimsPrincipal;
var ctx = Request.GetOwinContext();
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(id);
}
after login pass the User table value in this function
SignInAsync(result);
you can get clam value like
var identity = (ClaimsPrincipal)Thread.CurrentPrincipal;
// Get the claims values
string UserRoleValue = identity.Claims.Where(c => c.Type == ClaimTypes.Role)
.Select(c => c.Value).SingleOrDefault();
You can, in fact, create claims at the same time you create the user account.
Just add the claims to the user object before you call CreateAsync on the user manager.
var identityUser = new IdentityUser
{
UserName = username,
Email = email,
// etc...
Claims = { new IdentityUserClaim { ClaimType = "SomeClaimType", ClaimValue = "SomeClaimValue"} }
};
var identityResult = await _userManager.CreateAsync(identityUser, password);
This will create the user and associate the claims with the user as one logical operation with persistence.
I am working with MVC 3 and I have just implemented a wrapper for the FormsAuthenticationService.
Something similar to the following.
public void SignIn(string username, bool createPersistantCookie)
{
if (string.IsNullOrEmpty(username))
throw new ArgumentException("Value Cannot be null or empty", "username");
FormsAuthentication.SetAuthCookie(username, createPersistantCookie);
}
Reluctantly, I have gotten this to work, but now I am not quite sure how to get the information that I have stored.
Once the user is in my system, how can I now safely retrieve this information if I need to grab their UserID out of the database?
Based on the additional information provided, you want to store additional data with the FormsAuthentication ticket. To do so, you need first create a custom FormsAuthentication ticket:
Storing Data
Grab the current HttpContext (not worrying about testability)
var httpContext = HttpContext.Current;
Determine when the ticket should expire:
var expires = isPersistent
? DateTime.Now.Add(FormsAuthentication.Timeout)
: NoPersistenceExpiryDate; // NoPersistenceExpiryDate = DateTime.MinValue
Create a new FormsAuthentication ticket to hold your custom data.
var authenticationTicket = new FormsAuthenticationTicket(
1,
username,
DateTime.Now,
DateTime.Now.Add(FormsAuthentication.Timeout),
isPersistent,
"My Custom Data String"); //Limit to about 1200 bytes max
Create your HTTP cookie
new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(authenticationTicket))
{
Path = FormsAuthentication.FormsCookiePath,
Domain = FormsAuthentication.CookieDomain,
Secure = FormsAuthentication.RequireSSL,
Expires = expires,
HttpOnly = true
};
And finally add to the response
httpContext.Response.Cookies.Add(cookie);
Retrieving Data
Then you can retrieve your data on subsequent requests by parsing the stored authentication ticket...
Again, grab current HttpContext
var httpContext = HttpContext.Current
Check to see if the request has been authenticated (call in Application_AuthenticateRequest or OnAuthorize)
if (!httpContext.Request.IsAuthenticated)
return false;
Check to see if you have a FormsAuthentication ticket available and that it has not expired:
var formsCookie = httpContext.Request.Cookies[FormsAuthentication.FormsCookieName];
if (formsCookie == null)
return false;
Retrieve the FormsAuthentication ticket:
var authenticationTicket = FormsAuthentication.Decrypt(formsCookie.Value);
if (authenticationTicket.Expired)
return false;
And finally retrieve your data:
var data = authenticationTicket.UserData;
You haven't actually stored a user id in the database. All the code that you've written does is store an authentication cookie on the users computer, either as a session cookie (not persistent) or as a persistent one.
When your page refreshes, it will get the cookie automatically, decode it, and populate the IPrincipal object which you access from the User.Current property of your controller.