My WebAPI 2 application has a custom authorization filter which checks for an access token. If the token is present, and the API has the attribute, then I check if there exists a user which maps to that token.
Due to the nature of the API, most methods run in the context of a particular user (i.e. "POST api/profile" to update a user's profile). In order to do this, I need information on the target user which I get from the access token.
[Current Implementation, happens within attribute of type AuthorizeAttribute]
if( myDBContext.MyUsers.Count( x => x.TheAccessToken == clientProvidedToken ) ){
IPrincipal principal = new GenericPrincipal( new GenericIdentity( myAccessToken ), new string[] { "myRole" } );
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
return true;
}
This works fine, and I'm able to then use the access token to do a second lookup in the Method. But since I already do a lookup at auth time, I don't want to waste another DB call.
[What I'd like to do (but obviously doesn't work)]
MyUser user = myDBContext.MyUsers.FirstOrDefault( x => x.TheAccessToken == clientProvidedToken );
if( user != null ){
// Set *SOME* property to the User object, such that it can be
// access in the body of my controller method
// (e.g. /api/profile uses this object to load data)
HttpContext.Current.User = user;
return true;
}
You could use your own principal class. Maybe something like:
public class MyPrincipal : GenericPrincipal
{
public MyPrincipal(IIdentity identity, string[] roles)
: base(identity, roles)
{
}
public MyUser UserDetails {get; set;}
}
Then your action filter could do:
MyUser user = myDBContext.MyUsers.FirstOrDefault( x => x.TheAccessToken == clientProvidedToken );
if(user != null)
{
MyPrincipal principal = new MyPrincipal( new GenericIdentity( myAccessToken ), new string[] { "myRole" } );
principal.UserDetails = user;
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
return true;
}
return false;
And subsequently in your actual method, you can take the current user, check if it's of type MyPrincipal and if so cast it and then access the UserDetails:
...
MyUser currentUser = null;
MyPrincipal curPrincipal = HttpContext.Current.User as MyPrincipal;
if (curPrincipal != null)
{
currentUser = curPrincipal.UserDetails;
}
...
I haven't actaully tried this code, so there might be typos...
You could use a ClaimsIdentity/ClaimsPrincipal and add the Claims you need later in your controller, for example the actors ID or other values you need.
I've made an example which sets claims to the Actor but if it better suits you you could also at Claims directly to the current User.
var identity = new ClaimsIdentity(HttpContext.Current.User.Identity);
identity.Actor = new ClaimsIdentity();
identity.Actor.AddClaim(new Claim("Your", "Values"));
var principal = new ClaimsPrincipal(identity);
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = Thread.CurrentPrincipal;
Related
I was able to implement the below JWT solution in my MVC WebApi following below link JWT Authentication for Asp.Net Web Api
Now, I want to access claim in the controllers, but all claims are null. I have tried a few things and all of them returns null.
How is claim added in JwtAuthenticationAttribute:
protected Task<IPrincipal> AuthenticateJwtToken(string token)
{
string username;
if (ValidateToken(token, out username))
{
//Getting user department to add to claim.
eTaskEntities _db = new eTaskEntities();
var _user = (from u in _db.tblUsers join d in _db.tblDepartments on u.DepartmentID equals d.DepartmentID select u).FirstOrDefault();
var department = _user.DepartmentID;
// based on username to get more information from database in order to build local identity
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.SerialNumber, department.ToString())
// Add more claims if needed: Roles, ...
};
var identity = new ClaimsIdentity(claims, "Jwt");
IPrincipal user = new ClaimsPrincipal(identity);
return Task.FromResult(user);
}
return Task.FromResult<IPrincipal>(null);
}
What I have tried so far
Extension method to get claim:
public static class IPrincipleExtension
{
public static String GetDepartment(this IIdentity principal)
{
var identity = HttpContext.Current.User.Identity as ClaimsIdentity;
if (identity != null)
{
return identity.FindFirst("SerialNumber").Value;
}
else
return null;
}
}
Using the function defined in the post (link above):
TokenManager.GetPrincipal(Request.Headers.Authorization.Parameter).FindFirst("SerialNumber")
Trying to access Claim through thread:
((ClaimsPrincipal)System.Threading.Thread.CurrentPrincipal.Identity).FindFirst("SerialNumber")
For all the above, a claim is always null. What am I doing wrong?
You should try this to get your claim:
if (HttpContext.User.Identity is System.Security.Claims.ClaimsIdentity identity)
{
var serialNum = identity.FindFirst(System.Security.Claims.ClaimTypes.SerialNumber).Value;
}
I reserve the identity.FindFirst("string_here").Value for when I set my claims like this:
var usersClaims = new[]
{
new Claim("string_here", "value_of_string", ClaimValueTypes.String),
...
};
rather than using "prebuilt" values like this:
var usersClaims = new[]
{
new Claim(ClaimTypes.Name, "value_of_name", ClaimValueTypes.String),
...
};
In my ASP.NET MVC 5 application I need to use custom Authentication. Basically a custom library on which I call a method and which returns an object that contains information about the user.
I've created a new MVC 5 application and selected the "No Authentication" option. Then I've added an Http Module which currently looks like this:
private void Context_AuthenticateRequest(object sender, EventArgs e)
{
// Make the call to authenticate.
// This returns an object with user information.
AuthResult result = new AuthLib().SignOn();
// Inspect the returned object and create a list claims.
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, result.Username),
new Claim(ClaimTypes.GivenName, result.Name)
}
claims.AddRange(result.Groups.Select(g => new Claim(ClaimType.Role, g));
// Create principal and attach to context
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Sso");
HttpContext.Current.User = principal;
Thread.CurrentPrincipal = principal;
}
private void Context_PostAuthenticateRequest(object sender, EventArgs e)
{
var principal = ClaimsPrincipal.Current;
ClaimsAuthenticationManager transformer = FederatedAuthentication.SessionAuthenticationModule.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager;
transformer.Authenticate(string.Empty, principal);
}
My claimstransformer looks like this:
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
if (!incomingPrincipal.Identity.IsAuthenticated)
{
return base.Authenticate(resourceName, incomingPrincipal);
}
ClaimsPrincipal newPrincipal = CreateApplicationPrincipal(incomingPrincipal);
EstablishSession(newPrincipal);
return newPrincipal;
}
private void EstablishSession(ClaimsPrincipal newPrincipal)
{
var sessionToken = new SessionSecurityToken(newPrincipal, TimeSpan.FromHours(8));
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionToken);
}
private ClaimsPrincipal CreateApplicationPrincipal(ClaimsPrincipal incomingPrincipal)
{
// Convert AD group to known role in our application.
string group = incomingPrincipal.FindFirst(ClaimTypes.Role).Value;
string role = new ADGroupToRoleConverter().ConvertADGroupToRole(group);
// Add claims for group.
// These would be loaded from a db.
List<Claim> claims = new ClaimDb().GetClaimsForRole(role);
// Just copy the claims for id and given name.
claims.Add(incomingPrincipal.FindFirst(ClaimTypes.NameIdentifier));
claims.Add(incomingPrincipal.FindFirst(ClaimTypes.GivenName));
return new ClaimsPrincipal(new ClaimsIdentity(claims, "MyApp"));
}
The main issue that I'm facing is that the authentication step is called for every request even though a session exists. How can I detect that a session exists and just load the session instead of going through the entire authentication process.
Another issue is that the call to the authentication library might take a while. I guess ideally it should also be moved to the claims transformer?
Any ideas to improve this code further are also very much appreciated.
Please let me know if something is not clear or if I need to provide more detailed information.
It seems to me that you do not provide authentication information with each request after the authentication. Can you verify that you have some session cookie or authentication header sent with each request after the authentication happens?
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 have the function AffiliateLogin in a controller that sets the Principal.
the row principal.User = user; is actually the one storing the Principal.
But after I redirect to another controller, and test my AuthorizeWithRolesAttribute attribute, the principal is reset.
This is one second after the login, you can see the red arrow:
this is the function that stores it.
What am I doing wrong?
Thanks
public JsonResult AffiliateLogin(string email, string password)
{
if (ModelState.IsValid)
{
Affiliate user = api.GetUserByCredencials<Affiliate>(email, password);
if (user != null)
{
IIdentity identity = new UserIdentity(true,user.Email);
UserPrincipal principal = new UserPrincipal(identity, new string[] {"Affiliate"});
principal.User = user;
HttpContext.User = principal;
return Json("Login success");
}
}
return Json("Fail To Login");
}
The principal property won't survive between web requests. You had to set it again in the next request after redirection.
If your doing doing custom authentication/forms authentication you should call
FormsAuthentication.SetAuthCookie
The next http from the browser with that cookie , Asp.net will process the cookie and set
the current claims principal. So you can check
var principal = ClaimsPrincipal.Current; //normally this reverts to Thread.CurrentPrincipal,
Here is a good place to learn a bit more
http://msdn.microsoft.com/en-us/library/system.security.claims.claimsprincipal.current
The problem i see with this code, is that it's going to be reused a lot; anything being edited/created by a authenticated user (except for Site administrators) will only have access to a their "studios" objects.
My question to you all; how would you re-factor this so the service layer can be abstracted away from the knowledge of the client. I intend to reuse the service layer in a stand-alone desktop application later.
Please shed some light on my erroneous ways! I greatly appreciate it.
AuthorizeOwnerAttribute.cs (AuthorizeAttribute)
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// Get the authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = httpContext.Request.Cookies[cookieName];
// If the cookie can't be found, don't issue the ticket
if (authCookie == null) return false;
// Get the authentication ticket and rebuild the principal & identity
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
string[] userData = authTicket.UserData.Split(new[] { '|' });
int userId = Int32.Parse(userData[0]);
int studioID = Int32.Parse(userData[1]);
GenericIdentity userIdentity = new GenericIdentity(authTicket.Name);
WebPrincipal userPrincipal = new WebPrincipal(userIdentity, userId, studioID);
httpContext.User = userPrincipal;
return true;
}
Inside of my "User" Controller attach this attribute to any method that requires an owner
[AuthorizeOwner]
public ActionResult Edit(int Id)
{
IUser user = userService.GetById(HttpContext.User, Id);
return View(user);
}
Now, in my Service layer is where I'm checking the passed down IPrincipal if it has access to the object being requested. This is where it's getting smelly:
UserService.cs
public IUser GetById(IPrincipal authUser, int id)
{
if (authUser == null) throw new ArgumentException("user");
WebPrincipal webPrincipal = authUser as WebPrincipal;
if (webPrincipal == null) throw new AuthenticationException("User is not logged in");
IUser user = repository.GetByID(id).FirstOrDefault();
if (user != null)
{
if (user.StudioID != webPrincipal.StudioID) throw new AuthenticationException("User does not have ownership of this object");
return user;
}
throw new ArgumentException("Couldn't find a user by the id specified", "id");
}
I'm not sure I'd be storing the actual IDs in the cookie, that's a little too exposed. I'd be more inclined to use the Session hash to store that data thus keeping it on the server and not exposed.
I'd also use the Model (by passing the userID) to determine which objects to return, i.e. those that have a matching studioID. That way your controller would only ever have to call "GetObjects(int id)", if they don't have access to anything then you get a null or empty collection back. That feels cleaner to me.