I am using Asp.Net Identity for authentication and my requirement is after login currently I can access only User.Identity.Name which is username only.
Is there any way I can add more properties after login like
User.Identity.UserType
User.Identity.DepartmentId
Reason why I wanna use this to avoid Session to identify the user on Views.
First, you need add custom claim to user. For exmaple after creating user:
await _userManager.Value.AddClaimAsync(user, new Claim("UserType", "SomeType"));
Then create an extension method to read claim value:
public static string GetUserType(this IIdentity identity)
{
if (identity == null)
throw new ArgumentNullException(nameof(identity));
var claim = ((ClaimsIdentity)identity).FindFirst("UserType")?.Value;
return claim ?? string.Empty;
}
And you after login you can access user type:
var userType = User.Identity.GetUserType();
And here is a generic extension to get custom claim:
public static T Get<T>(this IIdentity identity, string propertyName)
{
if (identity == null)
throw new ArgumentNullException(nameof(identity));
var value = ((ClaimsIdentity)identity).FindFirst(propertyName)?.Value;
if (value == null)
return default(T);
var type = typeof(T);
if (type.IsEnum)
return (T)Enum.Parse(typeof(T), value);
return (T)Convert.ChangeType(value, typeof(T));
}
For example, you have an enum for UserType:
public enum UserType
{
Admin,
Editor,
User,
}
var userType = User.Identity.Get<UserType>("UserType");
var representativeId = User.Identity.Get<int>("DepartmentId");
Related
I've been trying to setup a new IdentityServer3 with AspNetIdentity for a few days now. I'm able to login using my existing Identity DB and that's all good but I can never get the User.Identity.Name to contain data.
I've tried multiple attempts at adding custom claims & scopes and adding scopes to clients.
Finally, I loaded up the IdentityServer3 Sample repository and tested it out with the webforms client project since it already used the User.Identity.Name in it's About page.
Using WebForms sample client + AspNetIdentity sample server = User.Identity.Name is always null
Using WebForms sample client + SelfHost with Seq sample server = User.Identity.Name with data
I've tried other sample host projects that all populate the User.Identity.Name value just fine.
Now, on the client side I've written a workaround to pull the 'preferred_username' claim value and set the 'name' claim with it.
var id = new claimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);
//set the User.Identity.Name value
var name = id.Claims.Where(x => x.Type == "name").Select(x => x.Value).FirstOrDefault() ??
id.Claims.Where(x => x.Type == "preferred_username").Select(x => x.Value).FirstOrDefault();
id.AddClaim(new Claim("name", name));
My questions are:
Why doesn't the AspNetIdentity package fill this by default?
And what do I need to change on the server side so that I don't need to change the client?
public static IEnumerable<ApiResource> GetApis()
{
return new ApiResource[]
{
new ApiResource("MyApi", "My Admin API")
{
UserClaims = { JwtClaimTypes.Name, JwtClaimTypes.Email }
}
};
}
In Identityserver4 you can add the UserClaims to your resource. Fixed it for me.
On IdentityServer4 you can implement IProfileService on server and add the Claim in GetProfileDataAsync
public class AspNetIdentityProfileService : IProfileService
{
protected UserManager<ApplicationUser> _userManager;
public AspNetIdentityProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//Processing
var user = _userManager.GetUserAsync(context.Subject).Result;
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
};
context.IssuedClaims.AddRange(claims);
//Return
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context)
{
//Processing
var user = _userManager.GetUserAsync(context.Subject).Result;
context.IsActive = (user != null) && ((!user.LockoutEnd.HasValue) || (user.LockoutEnd.Value <= DateTime.Now));
//Return
return Task.FromResult(0);
}
}
Then add "AddProfileService()" to your ConfigureServices method.
services.AddIdentityServer(...)
...
.AddProfileService<AspNetIdentityProfileService>();
I have a Asp.net web api 2 project. In this project I use OWIN authentication.
I have two kinds of users.
One type of are those who logs in with user name and password, another type are those who logs in with mobile number and a four character word.
I want both of these users go the address /token to get their token, my implementation so far is like this :
This is start up class :
var provider = new AuthorizationServerProvider();
var options = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = provider
};
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
using (DbContext dbContext = new DbContext ())
{
var user = dbContext.User
.Where(a => a.UserName == context.UserName)
.Where(a => a.Password == context.Password)
.Select(a => new UserClaim
{
Id = a.Id,
UserName = a.UserName,
FirstName = a.FirstName,
LastName = a.LastName,
Roles = a.UserInRoles.Select(w => w.Role.Id).ToList()
}).FirstOrDefault();
if (user == null)
{
context.SetError("invalid grant", "Provided username and password is incorrect.");
return;
}
identity.AddUserClaim(user);
context.Validated(identity);
return;
}
}
}
This solution is for users who want to log in with user name , but what about those users who want to log in with mobile number, what should I do ?
You need to provide two instance of OAuthAuthorizationServerOptions one for authorizing with username and password and one for mobileNumber and code, and then add this two options via authorization middleware to your owin pipeline.
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
// rest of your code
var userAndPasswordOptions = new OAuthAuthorizationServerOptions(){ ... };
var mobileAndCodeOptions = new OAuthAuthorizationServerOptions(){ ... };
app.UseOAuthAuthorizationServer(userAndPasswordOptions);
app.UseOAuthAuthorizationServer(mobileAndCodeOptions);
// rest of your code
}
}
but you should know in this case these two providers answers to different request Endpoint.
If you need to have one endpoint to provide both type of authorization you can change your GrantResourceOwnerCredentials method in OAuthAuthorizationServerProvider.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
var form = await context.Request.ReadFormAsync().Result;
if (form["type"] == "mobile")
{
//validate mobileNumber and code
}
else
{
//validate username and password
}
identity.AddUserClaim(user);
context.Validated(identity);
return;
}
Using the [Authorize] attribute on an ASP.Net Web API method causes a "401 Unauthorized" response.
I have an Http Module that handles the context.AuthenticateRequest event in which I examine the Authorization header (Basic authorization) of the request, and, if valid, set the System.Threading.Thread.CurrentPrincipal to a new GenericPrincipal containing a new GenericIdentity based on the info in the Authorization header. I also set the HttpContext.Current.User to the same instance of GenericPrincipal.
At this point, the IsAuthenticated property of the IIdentity is true. However, by the time the action method in the controller is invoked, System.Threading.Thread.CurrentPrincipal has been set to a System.Security.Claims.ClaimsPrincipal containing a System.Security.Claims.ClaimsIdentity with IsAuthenticated = false.
So... somewhere in the pipeline between the point where I set the CurrentPrincipal and when it reaches the action method, the CurrentPrincipal and the Identity is getting replaced.
Some of the methods of the API access ASP.Net Identity users (for a related website, the API itself does not use ASP.Net Identity for authentication/authorization), so the API project is set up with all the relevant ASP.Net Identity NuGet packages, etc.
I've used the same Http Module in other API projects that DON'T have all the ASP.Net Identity NuGet packages, etc. and it works like a champ.
I suspect that the ASP.Net Identity configuration is causing my Basic Authentication System.Security.Claims.ClaimsPrincipal to be replaced.
Any help would be much appreciated!
Here's my code:
Http Module - at the end of this method, System.Threading.Thread.CurrentPrincipal and HttpContext.Current.User are correctly set.
public class FsApiHttpAuthentication : IHttpModule, IDisposable {
public void Init( HttpApplication context ) {
context.AuthenticateRequest += AuthenticateRequests;
context.EndRequest += TriggerCredentials;
}
private static void AuthenticateRequests( object sender, EventArgs e ) {
string authHeader = HttpContext.Current.Request.Headers["Authorization"];
if ( authHeader != null ) {
System.Net.Http.Headers.AuthenticationHeaderValue authHeaderVal = System.Net.Http.Headers.AuthenticationHeaderValue.Parse(authHeader);
if ( authHeaderVal.Parameter != null ) {
byte[] unencoded = Convert.FromBase64String(authHeaderVal.Parameter);
string userpw = System.Text.Encoding.GetEncoding("iso-8859-1").GetString(unencoded);
string[] creds = userpw.Split(':');
CredentialCache.Credential cred = CredentialCache.GetCredential(creds[0], creds[1]);
if ( cred != null ) {
System.Security.Principal.GenericIdentity identity = new System.Security.Principal.GenericIdentity
(cred.Username);System.Threading.Thread.CurrentPrincipal = new System.Security.Principal.GenericPrincipal(identity, roles);
if ( string.IsNullOrWhiteSpace(cred.RolesList) ) {
System.Threading.Thread.CurrentPrincipal = new System.Security.Principal.GenericPrincipal(identity, null);
} else {
System.Threading.Thread.CurrentPrincipal = new System.Security.Principal.GenericPrincipal(identity, cred.RolesList.Split(','));
}
HttpContext.Current.User = System.Threading.Thread.CurrentPrincipal;
}
}
}
}
Api Controller - when the Post action in this controller is reached, System.Threading.Thread.CurrentPrincipal and HttpContext.Current.User have been set to a System.Security.Claims.ClaimsPrincipal containing a System.Security.Claims.ClaimsIdentity with IsAuthenticated = false.
public class ConsumerAccountController : ApiController {
private ApplicationUserManager _userManager;
private ApplicationUserManager UserManager {
get {
return _userManager ?? Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
set {
_userManager = value;
}
}
public async System.Threading.Tasks.Task<IHttpActionResult> Post( API.FinancialSamaritan.com.ViewModels.UserCredentials creds ) {
API.FinancialSamaritan.com.ViewModels.CreateUserResult cccur = null;
try {
string username = creds.Username;
string password = creds.Password;
var user = new API.FinancialSamaritan.com.Models.ApplicationUser {
UserName = username,
Email = username,
SecurityQuestion = creds.SecurityQuestion,
SecurityAnswer = UserManager.PasswordHasher.HashPassword(creds.SecurityAnswer),
IsPasswordChangeRequired = true,
EmailConfirmed = true
};
IdentityResult userResult = await UserManager.CreateAsync(user, password);
...
The [Authorize] attribute was deriving from System.Web.Http.AuthorizeAttribute instead of System.Web.Mvc.AuthorizeAttribute. I had to fully qualify the namespace of the attribute so that MVC version of [Authorize] would be used. Thanks to #RonBrogan for pointing me in the right direction!
I have written a couple of ms lightswitch applications with forms authentication -> this creates aspnet_* tables in sql server.
How can I use the defined users, passwords, maybe even memberships, roles and application rights in a servicestack - application?
I have not tested this but I think it should get you started. Gladly stand corrected on any of my steps.
Things I think you will need to do..
In order to Authenticate against both 'systems' you'll need to set the Forms cookie and save your ServiceStack session.
Instead of calling FormsAuthentication.Authentiate() do something like below. This won't work until you complete all the steps.
var apiAuthService = AppHostBase.Resolve<AuthService>();
apiAuthService.RequestContext = System.Web.HttpContext.Current.ToRequestContext();
var apiResponse = apiAuthService.Authenticate(new Auth
{
UserName = model.UserName,
Password = model.Password,
RememberMe = false
});
Create a subclass of IUserAuthRepository (for retrieving membership/user/roles from aspnet_* tables and filling ServiceStack AuthUser).
CustomAuthRepository.cs (incomplete, but should get you started)
public class CustomAuthRepository : IUserAuthRepository
{
private readonly MembershipProvider _membershipProvider;
private readonly RoleProvider _roleProvider;
public CustomAuthRepository()
{
_membershipProvider = Membership.Provider;
_roleProvider = Roles.Provider;
}
public UserAuth GetUserAuthByUserName(string userNameOrEmail)
{
var user = _membershipProvider.GetUser(userNameOrEmail, true);
return new UserAuth {FirstName = user.UserName, Roles = _roleProvider.GetRolesForUser(userNameOrEmail).ToList() //FILL IN REST OF PROPERTIES};
}
public bool TryAuthenticate(string userName, string password, out UserAuth userAuth)
{
//userId = null;
userAuth = GetUserAuthByUserName(userName);
if (userAuth == null) return false;
if (FormsAuthentication.Authenticate(userName, password))
{
FormsAuthentication.SetAuthCookie(userName, false);
return true;
}
userAuth = null;
return false;
}
//MORE METHODS TO IMPLEMENT...
}
Wire Authentication up for ServiceStack in AppHost configure method.
var userRep = new CustomAuthRepository();
container.Register<IUserAuthRepository>(userRep);
Plugins.Add(
new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new CredentialsAuthProvider()
}
));
I am new MVC 3 user and I am trying to make admin through SQL database.
First of all, I have Customer entity and admin can be defined through admin field which is boolean type in Customer entity.
I want to make to access admin only in Product page, not normal customer.
And I want to make [Authorize(Roles="admin")] instead of [Authorize].
However, I don't know how can I make admin role in my code really.
Then in my HomeController, I written this code.
public class HomeController : Controller
{
[HttpPost]
public ActionResult Index(Customer model)
{
if (ModelState.IsValid)
{
//define user whether admin or customer
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["rentalDB"].ToString());
String find_admin_query = "SELECT admin FROM Customer WHERE userName = '" + model.userName + "' AND admin ='true'";
SqlCommand cmd = new SqlCommand(find_admin_query, conn);
conn.Open();
SqlDataReader sdr = cmd.ExecuteReader();
//it defines admin which is true or false
model.admin = sdr.HasRows;
conn.Close();
//if admin is logged in
if (model.admin == true) {
Roles.IsUserInRole(model.userName, "admin"); //Is it right?
if (DAL.UserIsVaild(model.userName, model.password))
{
FormsAuthentication.SetAuthCookie(model.userName, true);
return RedirectToAction("Index", "Product");
}
}
//if customer is logged in
if (model.admin == false) {
if (DAL.UserIsVaild(model.userName, model.password))
{
FormsAuthentication.SetAuthCookie(model.userName, true);
return RedirectToAction("Index", "Home");
}
}
ModelState.AddModelError("", "The user name or password is incorrect.");
}
// If we got this far, something failed, redisplay form
return View(model);
}
And DAL class is
public class DAL
{
static SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["rentalDB"].ToString());
public static bool UserIsVaild(string userName, string password)
{
bool authenticated = false;
string customer_query = string.Format("SELECT * FROM [Customer] WHERE userName = '{0}' AND password = '{1}'", userName, password);
SqlCommand cmd = new SqlCommand(customer_query, conn);
conn.Open();
SqlDataReader sdr = cmd.ExecuteReader();
authenticated = sdr.HasRows;
conn.Close();
return (authenticated);
}
}
Finally, I want to make custom [Authorize(Roles="admin")]
[Authorize(Roles="admin")]
public class ProductController : Controller
{
public ViewResult Index()
{
var product = db.Product.Include(a => a.Category);
return View(product.ToList());
}
}
These are my source code now. Do I need to make 'AuthorizeAttribute' class?
If I have to do, how can I make it? Could you explain to me? I cannot understand how to set particular role in my case.
Please help me how can I do. Thanks.
I know this question is a bit old but here's how I did something similar. I created a custom authorization attribute that I used to check if a user had the correct security access:
[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = false, Inherited = true)]
public sealed class AccessDeniedAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
// Get the roles from the Controller action decorated with the attribute e.g.
// [AccessDeniedAuthorize(Roles = MyRoleEnum.UserRole + "," + MyRoleEnum.ReadOnlyRole)]
var requiredRoles = Roles.Split(Convert.ToChar(","));
// Get the highest role a user has, from role provider, db lookup, etc.
// (This depends on your requirements - you could also get all roles for a user and check if they have the correct access)
var highestUserRole = GetHighestUserSecurityRole();
// If running locally bypass the check
if (filterContext.HttpContext.Request.IsLocal) return;
if (!requiredRoles.Any(highestUserRole.Contains))
{
// Redirect to access denied view
filterContext.Result = new ViewResult { ViewName = "AccessDenied" };
}
}
}
Now decorate the Controller with the custom attribute (you can also decorate individual Controller actions):
[AccessDeniedAuthorize(Roles="user")]
public class ProductController : Controller
{
[AccessDeniedAuthorize(Roles="admin")]
public ViewResult Index()
{
var product = db.Product.Include(a => a.Category);
return View(product.ToList());
}
}
Your Role.IsInRole usage isn't correct. Thats what the
[Authorize(Roles="Admin")] is used for, no need to call it.
In your code you are not setting the roles anywhere. If you want to do custom role management you can use your own role provider or store them in the auth token as shown here:
http://www.codeproject.com/Articles/36836/Forms-Authentication-and-Role-based-Authorization
note the section:
// Get the stored user-data, in this case, user roles
if (!string.IsNullOrEmpty(ticket.UserData))
{
string userData = ticket.UserData;
string[] roles = userData.Split(',');
//Roles were put in the UserData property in the authentication ticket
//while creating it
HttpContext.Current.User =
new System.Security.Principal.GenericPrincipal(id, roles);
}
}
However an easier approach here is to use the built in membership in asp.net.
Create a new mvc project using the 'internet application' template and this will all be setup for you. In visual studio click on the "asp.net configuration" icon above solution explorer. You can manage roles here and assignment to roles.