I'm using Identity Server 4 and I've customised my ASP.NET Identity user as follows:
public class ApplicationUser : IdentityUser
{
[MaxLength(100)]
public virtual string FirstName { get; set; }
[MaxLength(100)]
public virtual string LastName { get; set; }
}
I can't see where I would configure Identity Server 4 to include these 2 properties in the claims collection. I've had a look through some of the Identity Server 4 samples but can't see any examples.
I'd ideally like to map these 2 user properties to the given_name and family_name claims.
I'm currently hooking up to the notifications and querying the userinfo endpoint (hybrid flow?). So I'm not sure if this is configuration of Identity Server or customization of the userinfo endpoint?
In order to include your custom claims, you need to implement your own GetProfileDataAsync() method using the IProfileService. This method is being called everytime a user claim is requested.
Here is my implementation of IProfileService.
public class CustomProfileService : IProfileService
{
private readonly UserManager<User> _userManager;
public CustomProfileService(UserManager<User> userManager)
{
_userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var subjectId = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(subjectId);
if (user == null) return;
var claims = new List<Claim>
{
new Claim("username", user.UserName),
new Claim("email", user.Email),
new Claim("firstname", user.FirstName),
new Claim("lastname", user.LastName)
};
var roles = await _userManager.GetRolesAsync(user);
foreach (var role in roles)
{
claims.Add(new Claim("role", role));
}
var userClaims = await _userManager.GetClaimsAsync(user);
foreach (var userClaim in userClaims)
{
claims.Add(new Claim(userClaim.Type, userClaim.Value));
}
context.IssuedClaims = claims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
var user = await _userManager.FindByIdAsync(context.Subject.GetSubjectId());
context.IsActive = user.IsActive;
}
}
Then you will have to add this following line to Startup.ConfigureServices()
services.AddScoped<IProfileService, CustomProfileService>();
I was wondering why there is no documentation on this. It lead me to realise that I'm probably doing it wrong.
I'd not seen the table AspNetUserClaims created as part of ASP.NET Identity. I added my claim data into here and the claim data pulls through as part of the profile.
In the POST method for AccountController.Register I added:
var givenNameClaim = new IdentityUserClaim<string>()
{
ClaimType = "given_name",
ClaimValue = model.FirstName
};
var familyNameClaim = new IdentityUserClaim<string>()
{
ClaimType = "family_name",
ClaimValue = model.LastName
};
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
user.Claims.Add(givenNameClaim);
user.Claims.Add(familyNameClaim);
Here's what I've done: In AccountController.ExternalLoginCallback I added this:
//Add claim even if client didn't ask for it
additionalClaims.Add(new Claim(JwtClaimTypes.Email, "email#email.com"));
then I added the claim to the access_token by dependency injecting my ProfileService class and adding the claims in the MyProfileService.GetProfileDataAsync like this:
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var claims = new List<Claim>();
Claim emailClaim = context.Subject.Claims.Where<Claim>(claim => claim.Type.Equals(JwtClaimTypes.Email)).FirstOrDefault();
if (emailClaim != null)
{
claims.Add(emailClaim);
}
context.IssuedClaims = claims;
return Task.FromResult(0);
}
Related
I am using Identity Server 4.0 with Identity Core, I need user role along with token response.
below attached image for reference
It is not the correct concept due to OAuth2 protocol violation and requires modifying the internals of IS4.
But you could implement getting role from:
- User info endpoint (http://docs.identityserver.io/en/3.1.0/endpoints/userinfo.html)
- Access token
For the last variant, you should override profile service (http://docs.identityserver.io/en/3.1.0/reference/profileservice.html)|
For example:
public class MyProfileService : IProfileService
{
private readonly UserManager<User> _userManager;
public MyProfileService(UserManager<User> userManager)
{
_userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var user = await _userManager.GetUserAsync(context.Subject);
var roles = await _userManager.GetRolesAsync(user);
var claims = new List<Claim>
{
new Claim(JwtClaimTypes.Email, user.Email),
new Claim(JwtClaimTypes.GivenName, user.FirstName),
new Claim(JwtClaimTypes.FamilyName, user.LastName),
new Claim(JwtClaimTypes.Name, $"{user.FirstName} {user.LastName}"),
new Claim(JwtClaimTypes.Locale, user.PreferredLanguage)
};
claims.AddRange(roles.Select(role => new Claim(JwtClaimTypes.Role, role)));
context.IssuedClaims.AddRange(claims);
}
public Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
return Task.CompletedTask;
}
}
And don't forget to register it
services.AddIdentityServer(options =>
{
...
})
.AddProfileService<MyProfileService>()
I have a multitenant app secure with an IdentityServer4 implementation. I recently updated it to the latest ID4 and the behavior seems to have changed. Previously, I could make a request with the TokenClient inside of the IdentityModel package:
var parameters = new Dictionary<string, string>();
parameters.Add("username", loginModel.UserName);
parameters.Add("password", loginModel.Password);
var tokenClient = new TokenClient(new Uri(new Uri(accountsConfig.EndpointUrl), "/connect/token").ToString(), accountsConfig.ClientId, accountsConfig.Secret, null, AuthenticationStyle.PostValues);
var tokenResponse = await tokenClient.RequestCustomGrantAsync("AgentLogin", extra: parameters);
It would return all of the scopes defined for the client in the token. That is no longer the case. How do I configure ID4 to do that without explicitly requesting them inside of the TokenClient?
public class AgentLoginCustomGrantValidator : IExtensionGrantValidator
{
private readonly ILogger<AgentLoginCustomGrantValidator> _logger;
private readonly IAdminUserService _adminUserService;
public AgentLoginCustomGrantValidator(ILogger<AgentLoginCustomGrantValidator> logger, IAdminUserService adminUserService)
{
_logger = logger;
_adminUserService = adminUserService;
}
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
try
{
var username = context.Request.Raw.Get("username");
var password = context.Request.Raw.Get("password");
var userId = _adminUserService.AuthenticateUser(username, password);
if (userId != null)
{
var agencyUser = _adminUserService.GetUser(userId.Value);
context.Result = new GrantValidationResult($"{userId}", GrantType, agencyUser.Roles.Select(x => new Claim(JwtClaimTypes.Role, x.Name)).Concat(new List<Claim>() { new Claim(JwtClaimTypes.Name, agencyUser.UserName) { } }));
}
else
{
_logger.LogWarning($"Bum creds: {username} ");
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidClient, "Invalid credentials");
}
}
catch (Exception ex)
{
_logger.LogError(ex.ToString());
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidClient, ex.Message);
}
}
public string GrantType => "AgentLogin";
}
Looks like Identity Server 4 by default only returns the requested identity or api resources for each client. However, this behaviour can be easily overridden to return all the scopes regardless whether they were requested in the token request or not. You can create a CustomClaimsService which inherits from the DefaultClaimsService.
public class CustomClaimsService : DefaultClaimsService
{
public CustomClaimsService(IProfileService profile, ILogger<DefaultClaimsService> logger) : base(profile, logger)
{
}
public override async Task<IEnumerable<Claim>> GetAccessTokenClaimsAsync(ClaimsPrincipal subject,
Resources resources, ValidatedRequest request)
{
var baseResult = await base.GetAccessTokenClaimsAsync(subject, resources, request);
var outputClaims = baseResult.ToList();
//If there are any allowed scope claims that are not yet in the output claims - add them
foreach (var allowedClientScope in request.Client.AllowedScopes)
{
if (!outputClaims.Any(x => x.Type == JwtClaimTypes.Scope && x.Value == allowedClientScope))
{
outputClaims.Add(new Claim(JwtClaimTypes.Scope, allowedClientScope));
}
}
return outputClaims;
}
}
Then just register it with the IdentityServerBuilder service container.
var builder = services.AddIdentityServer(options =>
{
//Your identity server options
});
//Register the custom claims service with the service container
builder.Services.AddTransient<IClaimsService, CustomClaimsService>();
Each access token will now contain all the scopes that the given client is allowed.
I am working on Web API with AngularJS. I had implemented Web API token mechanism few days ago and able to login the application using the access token. I have used external DB table instead of ASP.NET identity table to authorize user.
I want to store user information in class so that it can be accessed easily from different controllers after User logged in. Currently I am using ClaimsIdentity in Controller Class to get the user information.
UserIdentityViewModel.cs
public class UserIdentityViewModel
{
public string UserName { get; set; }
public Guid UserId { get; set; }
}
Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
var myProvider = new AuthorizationServerProvider();
OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = myProvider
};
app.UseOAuthAuthorizationServer(options);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
AuthorizationServerProvider.cs
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated(); //
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
string userId = context.UserName;
string password = context.Password;
EmployeeAccessBLL chkEmpAccessBLL = new EmployeeAccessBLL();
EmployeeAccessViewModel vmEmployeeAccess = chkEmpAccessBLL.CheckEmployeeAccess(Convert.ToInt32(userId), password);
if(vmEmployeeAccess != null)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("username", vmEmployeeAccess.EmpName));
identity.AddClaim(new Claim("userid", Convert.ToString(vmEmployeeAccess.EmployeeId)));
UserIdentityViewModel vmUser = new UserIdentityViewModel();
vmUser.UserId = vmEmployeeAccess.EmployeeId;
vmUser.UserName = vmEmployeeAccess.EmpName;
context.Validated(identity);
}
else
{
context.SetError("invalid_grant", "Provided username and password is incorrect");
return;
}
}
}
EventController.cs
public class StreamEventController : ApiController
{
[Authorize]
[Route("api/addevent")]
[HttpPost]
public List<string> AddEvent(StreamEventViewModel vmEvent)
{
//Able to get User Information from Identity.Claims
var identity = (ClaimsIdentity)User.Identity;
string userId = identity.Claims
.Where(c => c.Type == "userid")
.Select(c => c.Value).FirstOrDefault();
//Not able to get User Information from following as new object instance gets created
UserIdentityViewModel vmUser = new UserIdentityViewModel();
vmEvent.CreatedBy = vmUser.UserId;
vmEvent.ModifiedBy = vmUser.UserId;
}
}
Instead of writing "Identity.Claims" in each method of every controller I want to use simple get/set approach or any other methodology to get User Information . The use of Static class is also bad in my opinion as it will store one information of user and multiple user login information gets missed.
Please help me and share with me the best approach that has been used in other Web API projects for login.
You can add a private variable which will be set in the constructor of the controller, like this:
// Should only be used in protected methods.
private ClaimsIdentity ThisUser = null;
public MyController()
{
if (User.Identity.IsAuthenticated)
ThisUser = (ClaimsIdentity)User.Identity;
}
[Authorize]
[Route("api/addevent")]
[HttpPost]
public List<string> AddEvent(StreamEventViewModel vmEvent)
{
string userId = ThisUser.FindFirstValue("userid");
}
Or create a User class where you load all properties:
private UserClass ThisUser = null;
public MyController()
{
if (User.Identity.IsAuthenticated)
ThisUser = new UserClass(User);
}
[Authorize]
[Route("api/addevent")]
[HttpPost]
public List<string> AddEvent(StreamEventViewModel vmEvent)
{
string userId = ThisUser.UserId;
}
Where UserClass is something like:
public class UserClass
{
public string UserId { get; private set; }
public UserClass(IPrincipal user)
{
UserId = user.FindFirstValue("userid");
}
}
But this is just overhead for the same thing.
You can consider to move things to an extension. In that case you get something like:
public static class RequestExtensions
{
public static UserClass GetUser(this HttpRequestMessage request)
{
return new UserClass(request.GetOwinContext().Authentication.User);
}
public static ClaimsIdentiy GetUser2(this HttpRequestMessage request)
{
return new (ClaimsIdentity)request.GetOwinContext().Authentication.User;
}
}
Which you can call:
[Authorize]
[Route("api/addevent")]
[HttpPost]
public List<string> AddEvent(StreamEventViewModel vmEvent)
{
string userId = Request.GetUser.UserId;
string userId2 = Request.GetUser2.FindFirstValue("userid");
}
I think I would go for Request.GetUser2.FindFirstValue("userid");
The code is meant to give you an idea. I didn't test the code but I think it should work.
I am trying to create a Auth Server with IdentityServer 4 and ASP.net Core Identity backed by Entity Framework.
I have Users & Claims being stored in ASP.net identity tables on startup and Client, Resources stored in Identity Server tables.
When I am trying to get a token, I am getting the error attached in the screenshot.
Startup.cs
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
var connectionString = #"server=localhost;database=IdentityServer;trusted_connection=yes";
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
services.AddScoped<ApplicationUser>();
//services.AddScoped<SignInManager<ApplicationUser>>();
services.AddScoped<UserManager<ApplicationUser>>();
services.AddScoped<UserStore<ApplicationUser>>();
services.AddEntityFrameworkSqlServer();
services.AddDbContext<ApplicationDbContext>(builder =>
{
builder.UseSqlServer(connectionString, options => options.MigrationsAssembly(migrationsAssembly));
});
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services
.AddIdentityServer()
.AddProfileService<ProfileService>()
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
.AddTemporarySigningCredential()
.AddConfigurationStore(builder =>
builder.UseSqlServer(connectionString, options =>
options.MigrationsAssembly(migrationsAssembly)))
.AddOperationalStore(builder =>
builder.UseSqlServer(connectionString, options =>
options.MigrationsAssembly(migrationsAssembly)))
.AddAspNetIdentity<ApplicationUser>();
services
.AddMvcCore()
.AddJsonFormatters();
}
//This method gets called by the runtime.Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// this will do the initial DB population
InitializeDatabase(app);
loggerFactory.AddConsole();
app.UseIdentity();
app.UseIdentityServer();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
private static void InitializeDatabase(IApplicationBuilder app)
{
using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
scope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
var configContext = scope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
configContext.Database.Migrate();
if (!configContext.Clients.Any())
{
foreach (var client in Config.GetClients())
{
configContext.Clients.Add(client.ToEntity());
}
configContext.SaveChanges();
}
if (!configContext.IdentityResources.Any())
{
foreach (var resource in Config.GetIdentityResources())
{
configContext.IdentityResources.Add(resource.ToEntity());
}
configContext.SaveChanges();
}
var appContext = app.ApplicationServices.GetRequiredService<ApplicationDbContext>();
if (!appContext.Users.Any())
{
foreach (var user in Config.GetUsers())
{
var identityUser = new ApplicationUser();
var hash = new PasswordHasher<IdentityUser>().HashPassword(identityUser, user.Password);
identityUser.PasswordHash = hash;
identityUser.UserName = user.Username;
identityUser.NormalizedUserName = user.Username;
identityUser.Email = user.Username;
identityUser.NormalizedEmail = user.Username;
identityUser.EmailConfirmed = true;
foreach (var claim in user.Claims)
{
identityUser.Claims.Add(new IdentityUserClaim<string> { UserId = user.SubjectId, ClaimType = claim.Type, ClaimValue = claim.Value });
}
appContext.Users.Add(identityUser);
appContext.SaveChanges();
}
}
if (configContext.ApiResources.Any()) return;
foreach (var resource in Config.GetApiResources())
{
configContext.ApiResources.Add(resource.ToEntity());
}
configContext.SaveChanges();
}
}
}
ResourceOwnerPasswordValidator.cs
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IUserStore<ApplicationUser> _userStore;
public ResourceOwnerPasswordValidator(IUserStore<ApplicationUser> userStore, UserManager<ApplicationUser> userManager)
{
_userStore = userStore;
_userManager = userManager;
}
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
var user = await _userStore.FindByNameAsync(context.UserName, CancellationToken.None);
if (user != null && await _userManager.CheckPasswordAsync(user, context.Password))
{
context.Result = new GrantValidationResult(
subject: user.Id,
authenticationMethod: context.Request.GrantType,
claims: user.Claims.Select(c=>new Claim(c.ClaimType, c.ClaimValue)));
}
context.Result = new GrantValidationResult(
TokenRequestErrors.InvalidGrant,
"invalid custom credential");
}
}
I can't figure out why ResourceOwnerPasswordValidator is not being invoked.
Thanks for your help.
I am new to ASP.NET MVC 5 and so I am trying to use it as much as possible to learn it by practice.
So I am thinking of using the new OWIN implementation of ASP.NET MVC to implement the authentication and authorization of my project. That said, I am building the project in a way that it can work with various types of databases.
So far I have used generic ADO.NET elements (e.g. DbDataReader etc) and I have refused to use any ORM. So I am wondering if I can go ahead with using the new identity system of ASP.NET or will I be bound to Entity Framework and SQL Server if I do so?
Not that simple. Not that hard either.
You'll have to write your custom implementation of:
IUserStore<TUser>
IUserPasswordStore<TUser>
IUserTwoFactorStore<TUser>
IUserClaimStore<TUser>
IRoleStore<TRole>
IUserSecurityStampStore<TUser, string>
IUserRoleStore<TUser, string>
UserManager<TUser>
Then create your own user implementation, from IUser<TKey>, like:
public class MyUser : IUser<string>
{
public string Id { get; set; }
public string UserName { get; set; }
}
Finally, from NuGet, remove AspNet.Identity.EntityFramework, which will remove EntityFramework too if you're not using it elsewhere.
Wherever your code breaks, rewrite it to use your custom implementations.
Tip
Create a MyUserRepository which implements items from 1 to 7.
Then, create a MyUserManager which implements item 8.
It will be damn easy to wire that up in place of default AspNet.Identity.EntityFramework classes.
To piggy-back on what ALMMa said, when I was working on my own custom implementation, I found this article to be invaluable:
Overview of Custom Storage Providers for ASP.NET Identity
It details no only what Interfaces need to be implemented, but goes into detail on how to implement them and gives code sample references to an actual MySQL implementation.
You just need to override some classes in the following manner to get basic role based authentication working without Entity Framework and SQL..
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
}
public class ApplicationUser : IUser
{
public ApplicationUser()
{
Id = Guid.NewGuid().ToString();
Roles = new List<string>();
}
public virtual string Email { get; set; }
public List<string> Roles { get; set; }
public virtual string Password { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
public string Id { get; }
public string UserName { get; set; }
public virtual void AddRole(string role)
{
Roles.Add(role);
}
public virtual void RemoveRole(string role)
{
Roles.Remove(role);
}
}
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options,
IOwinContext context)
{
var manager =
new ApplicationUserManager(
new UserStoreService<ApplicationUser>(context.Get<ApplicationDbContext>().Users));
manager.PasswordHasher = new FusionPasswordHasher();
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = false,
RequireDigit = false,
RequireLowercase = false,
RequireUppercase = false
};
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
return manager;
}
public virtual async Task<IdentityResult> AddUserToRolesAsync(string userId, IList<string> roles)
{
var userRoleStore = (IUserRoleStore<ApplicationUser, string>) Store;
var user = await FindByIdAsync(userId).ConfigureAwait(false);
if (user == null)
throw new InvalidOperationException("Invalid user Id");
var userRoles = await userRoleStore.GetRolesAsync(user).ConfigureAwait(false);
// Add user to each role using UserRoleStore
foreach (var role in roles.Where(role => !userRoles.Contains(role)))
await userRoleStore.AddToRoleAsync(user, role).ConfigureAwait(false);
// Call update once when all roles are added
return await UpdateAsync(user).ConfigureAwait(false);
}
public virtual async Task<IdentityResult> RemoveUserFromRolesAsync(string userId, IList<string> roles)
{
var userRoleStore = (IUserRoleStore<ApplicationUser, string>) Store;
var user = await FindByIdAsync(userId).ConfigureAwait(false);
if (user == null)
throw new InvalidOperationException("Invalid user Id");
var userRoles = await userRoleStore.GetRolesAsync(user).ConfigureAwait(false);
// Remove user to each role using UserRoleStore
foreach (var role in roles.Where(userRoles.Contains))
await userRoleStore.RemoveFromRoleAsync(user, role).ConfigureAwait(false);
// Call update once when all roles are removed
return await UpdateAsync(user).ConfigureAwait(false);
}
}
If you want to read all the users in one short and store in the memory than you use the below style. And I strongly recommend you to read user only at the time of login for the you need to add your logic in "UserStoreService" class.
public class ApplicationDbContext : IDisposable
{
private ApplicationDbContext(IList<ApplicationUser> users)
{
Users = users;
}
public IList<ApplicationUser> Users { get; set; }
public void Dispose()
{
}
public static ApplicationDbContext Create()
{
//You can use any database and hook it here
var users = new List<ApplicationUser>
{
new ApplicationUser
{
UserName = "a#a.com",
Email = "a#a.com",
Password = "test",
Roles = new List<string> {"Admin", "Admin2"}
},
new ApplicationUser
{
UserName = "a#a2.com",
Email = "a#a2.com",
Password = "test2",
Roles = new List<string> {"Admin"}
}
};
return new ApplicationDbContext(users);
}
}
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var user = await userManager.FindAsync(context.UserName.ToLower(), context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
try
{
var oAuthIdentity = await userManager.CreateIdentityAsync(user, context.Options.AuthenticationType);
var cookiesIdentity = await userManager.CreateIdentityAsync(user,
CookieAuthenticationDefaults.AuthenticationType);
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"client_id", context.ClientId == null ? string.Empty : context.ClientId
},
{
"userName", context.UserName
}
});
var ticket = new AuthenticationTicket(oAuthIdentity, props);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
catch (Exception ex)
{
Trace.TraceError("FUSION Error ::: " + ex.Message + ex.InnerException);
Trace.TraceError(ex.Message);
}
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (var property in context.Properties.Dictionary)
if (property.Value != null)
context.AdditionalResponseParameters.Add(property.Key, property.Value);
return Task.FromResult<object>(null);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
context.Validated();
return Task.FromResult<object>(null);
}
}
public class AppPasswordHasher : IPasswordHasher
{
public string HashPassword(string password)
{
return password;
}
public PasswordVerificationResult VerifyHashedPassword
(string hashedPassword, string providedPassword)
{
if (hashedPassword == HashPassword(providedPassword))
return PasswordVerificationResult.Success;
return PasswordVerificationResult.Failed;
}
}
Method like "FindByNameAsync" ; where you need to read user from db on demand/login
public class UserStoreService<TUser> : IUserStore<TUser>,
IUserPasswordStore<TUser>,
IUserRoleStore<TUser>
where TUser : ApplicationUser
{
private readonly IList<TUser> _users;
public UserStoreService(IList<TUser> users)
{
_users = users;
}
public virtual Task SetPasswordHashAsync(TUser user, string passwordHash)
{
user.Password = passwordHash;
return Task.FromResult(0);
}
public virtual Task<string> GetPasswordHashAsync(TUser user)
{
return Task.FromResult(user.Password);
}
public virtual Task<bool> HasPasswordAsync(TUser user)
{
return Task.FromResult(user.Password != null);
}
public virtual Task AddToRoleAsync(TUser user, string roleName)
{
user.AddRole(roleName);
return Task.FromResult(0);
}
public virtual Task RemoveFromRoleAsync(TUser user, string roleName)
{
user.RemoveRole(roleName);
return Task.FromResult(0);
}
public virtual Task<IList<string>> GetRolesAsync(TUser user)
{
return Task.FromResult((IList<string>) user.Roles);
}
public virtual Task<bool> IsInRoleAsync(TUser user, string roleName)
{
return Task.FromResult(user.Roles.Contains(roleName));
}
public virtual void Dispose()
{
}
public virtual Task CreateAsync(TUser user)
{
user.CreatedTime = DateTime.Now;
user.UpdatedTime = DateTime.Now;
_users.Add(user);
return Task.FromResult(true);
}
public virtual Task UpdateAsync(TUser user)
{
// todo should add an optimistic concurrency check
user.UpdatedTime = DateTime.Now;
_users.Remove(user);
_users.Add(user);
return Task.FromResult(true);
}
public virtual Task DeleteAsync(TUser user)
{
return Task.FromResult(_users.Remove(user));
}
public virtual Task<TUser> FindByIdAsync(string userId)
{
return Task.FromResult(_users.FirstOrDefault(u => u.Id == userId));
}
public virtual Task<TUser> FindByNameAsync(string userName)
{
// todo exception on duplicates? or better to enforce unique index to ensure this
return Task.FromResult(_users.FirstOrDefault(u => u.Email == userName));
}
}
[Authorize(Roles = "Admin")]
public class RolesController : ApiController
{
public IEnumerable<string> Get()
{
return new[] {"value3", "value4"};
}
}
Source Code (github)
It is bound to Entity Framework and SQL Server by default, but you can easily plug in other data stores such as SharePoint, Windows Azure Storage Table Service, NoSQL databases, etc., and you get to retain control of the database schema.
Further Reading
Introduction to ASP.NET Identity