How to set a custom ClaimsPrincipal in MVC 5? - c#

I created a custom principal class
public class FacebookPrincipal : ClaimsPrincipal
{
public JObject Data { get; set; }
}
And I want to use it. When the user logs in, I tried to set
var fbP = new FacebookPrincipal { Data = user.Data };
Thread.CurrentPrincipal = fbP;
AuthenticationManager.User = fbP;
HttpContext.User = fbP;
It works right after I set it, but when I go ho home/index the value is lost
var user = HttpContext.GetOwinContext().Authentication.User;
var bbbb = this.User;
var cccc = ClaimsPrincipal.Current;
All the above methods return a Principal of type ClaimsPrincipal and casting to FacebookPrincipal returns null.
How do I set a custom principal?

ASP.NET Identity uses default ClaimsIdentityFactory to create before assigning ClaimsIdentity to User and Thread. You should create your own ClaimsIdentityFactory where you can add or manage additional information.
UserManager<IdentityUser> userManager
= new UserManager<IdentityUser>(new UserStore<IdentityUser>());
userManager.ClaimsIdentityFactory
= new MyClaimsIdentityFactory<IdentityUser>();
And the following code to create your implementation for ClaimsIdentity or its subclass.
public class MyClaimsIdentityFactory<IUser> : ClaimsIdentityFactory<IUser> where IUser : IdentityUser
{
public MyClaimsIdentityFactory(): base()
{
}
public override System.Threading.Tasks.Task<System.Security.Claims.ClaimsIdentity> CreateAsync(UserManager<IUser> manager, IUser user, string authenticationType)
{
// Override Creation of ClaimsIdentity and return it.
}
}
Make sure you absolutely need to subclass ClaimsIdentity. You can add additional info as Claims.
You shall use base.CreateAsync and merge the Claims to your created ClaimsIdentity.

•Make sure you absolutely need to subclass ClaimsIdentity. You can add additional info as Claims.
You should be careful about adding additional claims for supplementary information as a side effect can be a change to how the authorization policy will make decisions.

As suggested by #marisks, you can use IUserClaimsStore to store claims issued from third-party for your user. only if the custom claims access is the problem.
Moreover, to persist the identity between two requests, use following code.
// One can create one's own Identity here.
var identity = await
UserManager.CreateIdentityAsync(user,
DefaultAuthenticationTypes.ApplicationCookie);
// Or add custom claims. Claims Stored in IUserClaimStore
// are already populated by above creation.
identity.AddClaim(new Claim("ProfileDATA", "VALUE"));
AuthenticationManager.SignIn(new AuthenticationProperties()
{ IsPersistent = isPersistent }, identity);

Related

IdentityServer4 GetProfileDataAsync is not called for Identity Token, only Access Token

Using IdentityServer 4 (4.1.2), I've added a class implementing the IProfileService interface. The method GetProfileDataAsync is supposed to be called several times (for each token), but my method is only called for the Access Token (ClaimsProviderAccessToken).
public class LocalUserProfileService : IProfileService
{
private readonly IIdentityProviderUserService _identityProviderUserService;
public LocalUserProfileService(IIdentityProviderUserService identityProviderUserService)
{
_identityProviderUserService = identityProviderUserService ??
throw new ArgumentNullException(nameof(identityProviderUserService));
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var subjectId = context.Subject.GetSubjectId();
var claims = (await _identityProviderUserService.GetUserClaimsBySubjectAsync(subjectId)).ToList();
Debug.WriteLine($"Adding claims to {context.Caller}");
context.IssuedClaims.AddRange(claims);
}
public async Task IsActiveAsync(IsActiveContext context)
{
var subjectId = context.Subject.GetSubjectId();
context.IsActive = await _identityProviderUserService.IsUserActiveAsync(subjectId);
}
}
I could manage to only use my access token to get the custom claims, but I would like to know why the code is not called for the identity_token as I prefer to have the claims in both tokens.
Have you set the AlwaysIncludeUserClaimsInIdToken flag to true? Otherwise the claims for the ID-token will be provided through the UserInfo endpoint instead.
A common way to reduce the size of the ID-token is to not include all the claims in the ID-token. Large tokens will also result in large session cookies. By default, the tokens are stored inside the session cookie.

How can I register a custom Principal type in ASP.NET MVC 5?

I have an application that is written using C# on the top of the ASP.NET MVC 5 framework.
Instead of using the built-in ClaimsPrincipal implementation of the IPrincipal interface, I want to used MyOwnClaimsPrincipal implementation. My MyOwnClaimsPrincipal class implements the IPrincipal interface.
In other words, I want to be able to access MyOwnClaimsPrincipal instance from inside of AuthorizeAttribute class or as the User Property in the controller and the views.
here is an example of what I want to be able to do
public class TestAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
// This does not work
MyOwnClaimsPrincipal user1 = filterContext.HttpContext.User as MyOwnClaimsPrincipal;
// Yet, this works which tells me that my mplementation is not being used
ClaimsPrincipal user2 = filterContext.HttpContext.User as ClaimsPrincipal;
}
}
I tried to utilize Application_BeginRequest method in the Global.asax.cs to set the Thread.CurrentPrincipal and HttpContext.Current.User at the begining of each request like so
public void Application_BeginRequest()
{
var user = new MyOwnClaimsPrincipal();//...
Thread.CurrentPrincipal = user;
HttpContext.Current.User = user;
}
Unfortunately, that did not work. The filterContext.HttpContext.User is still set to ClaimsPrincipal and when I cast it as MyOwnClaimsPrincipal a null is returned.
How can I correctly set my one implementation of the IPrincipal to be used in the application?
I think you are to late according to this article Application_BeginRequest is the first event.
Just guessing: It seems the default authentification mechanism isn't checking if already an IPrincipal is assigned, so it overrides yours.
I have done the same using the Application_PostAuthenticateRequest Handler and it works fine:
protected void Application_PostAuthenticateRequest(object sender, EventArgs eventArgs)
{
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, "42");
claims.Add(new Claim(ClaimTypes.Name, "Hello World"));
var claimsIdentity = new ClaimsIdentity(claims, "MylAuthentificationSheme");
Context.User = new ClaimsPrincipal(claimsIdentity);
}

IdentityServer4 claims are not part of the token on hybrid flow

I'm trying to build a hybrid flow, and have claims on the returned access token with IdentityServer4. I'm using the QuickStart UI controlles.
In my AccountController after the user was authenticated successfully, I have the following code which signs him in:
await HttpContext.SignInAsync("anon#nymous.com", "anon#nymous.com", null, new Claim("MyName", "Ophir"));
In the MVC website that is causing this flow, on the page I want to "protect" I have the following code:
[Authorize]
public IActionResult RestrictedMvcResource()
{
var token = _httpContext.HttpContext.GetTokenAsync("access_token").Result;
var identity = User.Identity;
return View();
}
After a successful login, the debugger hits this code fine and I'm getting the access token.
The problem is that if I decode my access token (I'm using https://jwt.io/) I see the name and subject, but I do not see the MyName claim that I have defined.
(I have another flow in my system for client_credentials which does return the claims on the token - but it uses a different code flow).
How do I return the claims on the token for the hybrid flow?
EDIT:
Solving this problem was a combination of 2 things:
Implementing IProfileService as suggested in (selected) answer. here's my implementation:
public class ProfileService : IProfileService
{
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
context.AddRequestedClaims(context.Subject.Claims);
foreach (Claim claim in context.Subject.Claims)
{
if (context.IssuedClaims.Contains(claim))
continue;
context.IssuedClaims.Add(claim);
}
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
return Task.FromResult(0);
}
}
This will add any claim that isn't already on the token.
When calling HttpContext.SignInAsync you must pass the list of claims, otherwise no additional claims will be in the context.Subject.Claims collection.
You can implement custom IProfileService if you want to add custom claims to the token.
You can find more info in Identity Server 4 docs.
An example of simple custom profile service would be:
public class CustomProfileService : IProfileService
{
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
context.AddRequestedClaims(context.Subject.Claims);
context.IssuedClaims.Add(new Claim("MyName", "Ophir"));
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
return Task.FromResult(0);
}
}
Once you have this, just register it to the DI:
services.AddTransient<IProfileService, CustomProfileService>();
It will get called whenever an access_token or id_token is requested. You would need to check context.Caller as per Ruard's comment if you only wanted the extra claims in certain type of token.
EDIT:
Also alternatively, you can add the claims directly to the user configuration as per example in one of the Identity Server 4 quickstarts:
new TestUser
{
SubjectId = "1",
Username = "alice",
Password = "password",
Claims = new []
{
new Claim("MyName", "Ophir")
}
},
If you end up not implementing custom IProfileService and keep using DefaultProfileService, then you would also need add a custom IdentityResource in your configuration:
return new List<IdentityResource>
{
//..Your other configured identity resources
new IdentityResource(
name: "custom.name",
displayName: "Custom Name",
claimTypes: new[] { "MyName" });
};
Any clients wanting to have this claim added in the token would need request for custom.name scope.
AspNet.Security.OpenIdConnect.Server does not serialize claims that do not have destinations set. I ran into this when using OpenIdDict.
try this:
var claim = new Claim("MyName", "Ophir");
claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken);
await HttpContext.SignInAsync("anon#nymous.com", "anon#nymous.com", null, claim);
You will probably need to add these namespaces:
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
There are two steps in which I add claims to tokens for our identityserver.
Through a custom profile service like one of the other answers shows, you can define your own claims for an user.
Those claims can then be requested through the userinfo endpoint.
Or you create an Api ( resource) called for example IncludeNameInAccessToken that adds the name claim by default to the access token if you request that Api as a scope.

Customize asp.net identity role claim

I'm using asp.net identity with ClaimsPrincipal and ClaimsIdentity. The identity is created by a custom usermanager and sent to the clients as a bearer token.
All works fine, but I would like to instruct asp.net to use a custom claim type for role, instead of the standard System.Security.Claims.ClaimTypes.Role (http://schemas.microsoft.com/ws/2008/06/identity/claims/role).
Is it possibile?
Edit: I would like to use the standard Authorize attribute with role constructor, ex: Authorize(Roles="staff") and let the framework to check the claims, but my custom role claim type ("myapproleclaim") instead of the standard one.
You can do things like,
public class CustomPrinciple : ClaimsPrincipal
{
public CustomPrinciple(ClaimsIdentity identity) : base(identity)
{
}
public override bool IsInRole(string role)
{
return HasClaim("myRoleClaimType", role);
}
}
[TestClass]
public class CustomRoleTest
{
[TestMethod]
public void testing_custom_role_type()
{
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim("myRoleClaimType", "role1"));
var principle = new CustomPrinciple(identity);
Assert.IsTrue(principle.IsInRole("role1"));
Assert.IsFalse(principle.IsInRole("role2"));
}
}

Webapi with multiple oauth tokens for different users

I am in the process of creating my own webapi in asp.net using oauth as authorization provider.
The api wil basically serve as a provider for different modules as i call them. One could be a image gallery, the other could just be a user login module with different types of users.
I have the oauth part working fine. Api users can register and then ask for a Token by calling the /Token endpoint with the login credentials.
However i now want to create another seperate user module in the api that is only accessible by apiusers that registered . I want this module to have another register and login function and have their own endpoint to login (/UserModuleToken or something like that). The users coming from the user module are different users than the Api users. So the apiusers are the actual developers that want to call specific modules in my api, and the users from the user module are users that register on the site where that module is implemented.
All of my apicontrollers wil have the [Authorize] attribute for the api user, and i want specific ones, for example some function in the user module, to be decorated with [UserModuleAuthorize] attribute.
Below you can see my api user entity model:
public class ApiUserEntity : BaseEntity
{
public string Username { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string Salt { get; set; }
public ApiUserLevel Level { get; set; }
}
The userservice function that can validate an api user:
public UserLoginResult LoginUser(ApiUserEntityLoginForm userForm)
{
// retrieve user from database
var user = _userRepository.GetUser(userForm.UserName);
if(user == null)
return _modelStateWrapper.AddError(UserLoginResult.UserNotFound, "User does not exist");
var passwordHash = PasswordHash.HashPassword(user.Salt, userForm.Password);
// check if password matches with database.
if (passwordHash != user.Password)
return _modelStateWrapper.AddError(UserLoginResult.IncorrectPassword, "Incorrect password");
return UserLoginResult.Success;
}
And calling the /Token endpoint in my webapi will call the following function of the token provider:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
// create a userloginform object :
var loginForm = new ApiUserEntityLoginForm {UserName = context.UserName, Password = context.Password};
// pass it into the login validation function of the userservice:
var loginResult = _userService.LoginUser(loginForm);
// if login result was not sucesful, return an error.
if (loginResult != UserLoginResult.Success)
{
var jsonSerialiser = new JavaScriptSerializer();
var json = jsonSerialiser.Serialize(_userService.Errors());
context.SetError("invalid_grant", json);
return;
}
// result was succesful, grant the token.
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
i configure my oauth provider and define the /Token endpoint with the following function:
public static void ConfigureOAuth(IAppBuilder app, IUnityContainer container)
{
var simpleAuthorizationServerProvider = container.Resolve<SimpleAuthorizationServerProvider>();
var OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = simpleAuthorizationServerProvider
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
Now my question is if it is somehow possible to have multiple token endpoints so i can have a token for apiusers and then another one for a user that is using the custom user module and protect certain functionality based on those 2 users.
I couldnt find any information about this after an extensive amount of searching the internet. So im beginning to believe this is not good practice or not possible. If anyone would be able to point me in the right direction that would be great!
Well I believe you need to configure users authorization based on Roles, what you are trying to do is just complicating your solution.
What you can do is the following: inside method GrantResourceOwnerCredentials you need to obtain the correct role(s) for the authenticated user from the DB store i.e "Admin" and then add them as claims with type "Role" as the code below:
identity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
identity.AddClaim(new Claim(ClaimTypes.Role, "Supervisor"));
Now on your controllers that you want just user with role "Admin" to access; you need to attribute with [Authorize(Roles="Admin")] or maybe multiple roles [Authorize(Roles="Admin,User")]
This is the straightest way to achive your goal.
Btw this code from http://bitoftech.net, right? Glad to see my code samples used :)
Let me know if you need further clarifications.

Categories

Resources