How to change Owin.AuthenticationIdentityManager's PasswordValidator settings? - authentication

This autogenerated code registers a new user:
var manager = new AuthenticationIdentityManager(new IdentityStore());
User u = new User(userName) { UserName = userName };
IdentityResult result = manager.Users.CreateLocalUser(u, Password.Text);
if (result.Success)
{ ... }
How can we change password validation settings?
This is what I've found using dotPeek:
AuthenticationIdentityManager class instance contains UserManager which validates a password using PasswordValidator:
From class Microsoft.AspNet.Identity.UserManager:
result = this.PasswordValidator.Validate(password);
internal IStringValidator PasswordValidator
{
get
{
return this.Manager.Settings.GetIdentityOptions().PasswordValidator;
}
}
Class Microsoft.AspNet.Identity.IdentityOptions:
public IdentityOptions()
{
this._defaultPasswordValidator = new IdentityOptions.DefaultPasswordValidator(this);
this._defaultUserNameValidator = new IdentityOptions.DefaultUserNameValidator(this);
this.MinRequiredPasswordLength = 6;
this.LocalLoginProvider = "Local";
}
public IStringValidator PasswordValidator
{
get
{
return this._passwordValidator ?? (IStringValidator) this._defaultPasswordValidator;
}
set
{
this._passwordValidator = value;
}
}
I cannot find where IdentityOptions instance can be accessed or replaced. Is there any example or documentation?

RTM version seems to have dropped AuthenticationIdentityManager. Try upgrading the to latest pre-release version.
In the latest version, you can provide PasswordValidator implementation to UserManager. This will enable customization of password strength validation in your app.
ASP.NET Identity nighlty packages are now available on MyGet

Related

Custom OpenIddictApplication field always returns null

I am using OpenIDDict and extending OpenIddictEntityFrameworkCoreApplication to include my own custom field:
public class TenantApplication : OpenIddictEntityFrameworkCoreApplication<long, TenantAuthorization, TenantToken> {
public long? TenantID { get; set; }
}
public class TenantAuthorization : OpenIddictEntityFrameworkCoreAuthorization<long, TenantApplication, TenantToken> { }
public class TenantScope : OpenIddictEntityFrameworkCoreScope<long> { }
public class TenantToken : OpenIddictEntityFrameworkCoreToken<long, TenantApplication, TenantAuthorization> { }
I register under AddDbContext:
builder.Services.AddDbContext<ApplicationDbContext>(options => {
options.UseNpgsql(connectionString);
options.UseOpenIddict<TenantApplication, TenantAuthorization, TenantScope, TenantToken, long>();
});
and also under AddCore:
// Register the OpenIddict core components.
.AddCore(options =>
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>()
.ReplaceDefaultEntities<TenantApplication, TenantAuthorization, TenantScope, TenantToken, long>();
options.UseQuartz();
})
In my Worker, I create the application record if it doesn't exist, but TenantID is always inserted with null:
if (await manager.FindByClientIdAsync("postman", cancellationToken) is null)
{
await manager.CreateAsync(new TokenOpenIddictApplicationDescriptor
{
TenantID = 2,
ClientId = "postman",
ClientSecret = "388D45FA-B36B-4988-BA59-B187D329C207",
DisplayName = "My client application",
Permissions =
{
Permissions.Endpoints.Token,
Permissions.GrantTypes.ClientCredentials
}
});
Similarly, manager.FindByClientIdAsync also returns the Application instance with a null TenantID (after I manually set it in the db). What step am I missing?
I figured this out on my own by diving into the OpenIDDict source code. In order to extend the Application fields, I extended OpenIddictApplicationManager and OpenIddictEntityFrameworkCoreApplicationStore. OpenIddictApplicationManager has a PopulateAsync method which converts a OpenIddictApplicationDescriptor to an Application instance. You need to override this method so that your custom Application fields will be set.
When initializing everything in Program.cs you also need to add your custom store and replace the application manager.
options.AddApplicationStore<TenantOpenIddictEntityFrameworkCoreApplicationStore>();
options.ReplaceApplicationManager<TenantOpenIddictApplicationManager>();
Maybe there is a better way of doing this, but this worked for me. It would be nice if there was some more easier to find examples with more complex scenarios.

User getting created before custom user validator runs in Identity core

I am using identity core for user management in .net core 3.1 web api. Now, I want to check the users email for something and if it meets the requirement only then he will be created. The code below tells a lot about what I want to achieve
I have a custom user validator as below:
public class CustomEmailValidator<TUser> : IUserValidator<TUser>
where TUser : User
{
public Task<IdentityResult> ValidateAsync(UserManager<TUser> manager,
TUser user)
{
User userFromEmail = null;
if(!string.IsNullOrEmpty(user.Email))
userFromEmail = manager.FindByEmailAsync(user.Email).Result;
if (userFromEmail == null)
return Task.FromResult(IdentityResult.Success);
return Task.FromResult(
IdentityResult.Failed(new IdentityError
{
Code = "Err",
Description = "You are already registered with us."
}));
}
}
I add the validator in my startup as below:
services.AddDbContext<DataContext>(x => x.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
IdentityBuilder builder = services.AddIdentityCore<User>(opt =>
{
opt.User.RequireUniqueEmail = false;
opt.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyz0123456789._-";
opt.Password.RequireDigit = true;
opt.Password.RequiredLength = 6;
opt.Password.RequireNonAlphanumeric = true;
opt.Password.RequireUppercase = true;
opt.Password.RequireLowercase = true;
})
.AddUserValidator<CustomEmailValidator<User>>();
builder = new IdentityBuilder(builder.UserType, typeof(Role), builder.Services);
builder.AddEntityFrameworkStores<DataContext>();
builder.AddRoleValidator<RoleValidator<Role>>();
builder.AddRoleManager<RoleManager<Role>>();
builder.AddSignInManager<SignInManager<User>>();
As can be seen, I want to use the default user validation and my custom validation too. The problem being the user gets created right after the default validation and the email always turns out as exists in my custom validation. I don't really want to override my default validations.
Creating the user as below:
[HttpPost("Register")]
public async Task<IActionResult> Register(UserForRegisterDto userForRegister)
{
var userToCreate = _mapper.Map<User>(userForRegister);
var result = await _userManager.CreateAsync(userToCreate, userForRegister.Password);
if (result.Succeeded)
{
var roleresult = await _userManager.AddToRoleAsync(userToCreate, "Member");
return Ok(roleresult);
}
return BadRequest(result.Errors);
}
Note This is not my actual use case. I know I can check for unique email in my default validation by making opt.User.RequireUniqueEmail = true. This is just to clear a concept for further development.
Update After further debugging, I see that the custom validation method is called twice. Once before user creation and once after creation for some reason. I insert a new unique email and the custom validation passes success and after user creation, custom validation is called again and find the email registered already and throws an error message. This is weird
Found out that AddToRoleAsync was calling the custom validator again and was finding the user present in the table. Had to include a check whether the user found in the table with the same email is the same as user getting getting updated.
Code below:
public class CustomEmailValidator<TUser> : IUserValidator<TUser>
where TUser : User
{
public Task<IdentityResult> ValidateAsync(UserManager<TUser> manager,
TUser user)
{
User userFromEmail = null;
if(!string.IsNullOrEmpty(user.Email))
userFromEmail = manager.FindByEmailAsync(user.Email).Result;
if (userFromEmail == null)
return Task.FromResult(IdentityResult.Success);
else {
if(string.Equals(userFromEmail.Id, user.Id))
{
return Task.FromResult(IdentityResult.Success);
}
}
return Task.FromResult(
IdentityResult.Failed(new IdentityError
{
Code = "Err",
Description = "You are already registered with us."
}));
}
}
This should help a lot of people

RequireAuthorization and Swashbuckle IOperationFilter

I am looking for a way to determine if endpoint requires authorization (.Net Core 3.1) using IOperationFilter.
If Authorization is setup via filter or explicitly as attribute, it can be found in OperationFilterContext context.ApiDescription.ActionDescriptor.FilterDescriptors.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter) and context.ApiDescription.CustomAttributes().OfType<AuthorizeAttribute>().
But if authorization is set as
endpoints.MapControllers().RequireAuthorization();, which should add AuthorizationAttribute to all endpoints, it is not appeared neither in filters nor in attributes. Any thoughts on how to catch if auth is applied to endpoints in this case?
I was able to beat this today like so (swashbuckle 5.63):
Make a new class like this
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Authorization;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace YourNameSpace
{
public class SwaggerGlobalAuthFilter : IOperationFilter
{
public void Apply( OpenApiOperation operation, OperationFilterContext context )
{
context.ApiDescription.TryGetMethodInfo( out MethodInfo methodInfo );
if ( methodInfo == null )
{
return;
}
var hasAllowAnonymousAttribute = false;
if ( methodInfo.MemberType == MemberTypes.Method )
{
// NOTE: Check the controller or the method itself has AllowAnonymousAttribute attribute
hasAllowAnonymousAttribute =
methodInfo.DeclaringType.GetCustomAttributes( true ).OfType<AllowAnonymousAttribute>().Any() ||
methodInfo.GetCustomAttributes( true ).OfType<AllowAnonymousAttribute>().Any();
}
if ( hasAllowAnonymousAttribute )
{
return;
}
// NOTE: This adds the "Padlock" icon to the endpoint in swagger,
// we can also pass through the names of the policies in the List<string>()
// which will indicate which permission you require.
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "oauth2" // note this 'Id' matches the name 'oauth2' defined in the swagger extensions config section below
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
}
}
};
}
}
}
In swagger config extensions
options.AddSecurityDefinition( "oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
Implicit = new OpenApiOAuthFlow
{
//_swaggerSettings is a custom settings object of our own
AuthorizationUrl = new Uri( _swaggerSettings.AuthorizationUrl ),
Scopes = _swaggerSettings.Scopes
}
}
} );
options.OperationFilter<SwaggerGlobalAuthFilter>();
Put together from docs, other SO and decompiled code of built-in SecurityRequirementsOperationFilter
AFAIK, it is defining a global auth setup for all your routed endpoints except those that explicitly have AllowAnonymousAttribute on controller or endpoint. since, as your original question hints at, using the extension RequireAuthorization() when setting up routing implicitly puts that attribute on all endpoints and the built-in SecurityRequirementsOperationFilter which detect the Authorize attribute fails to pick it up. Since your routing setup effectively is putting Authorize on every controller/route it seems setting up a default global filter like this that excludes AllowAnonymous would be in line with what you are configuring in the pipeline.
I suspect there may be a more 'built-in' way of doing this, but I could not find it.
Apparently, this is an open issue on the NSwag repo as well (for people like me that drive by with the same issue, but with NSwag instead of Swashbuckle):
https://github.com/RicoSuter/NSwag/issues/2817
Where there's also another example of solving the issue (not only securityrequirement, but also its scopes).
I know it's been a long time since this question was asked.
But I was facing a similar issue, and following the advice from an issue in GitHub here, managed to resolve it using this implementation of IOperationFilter (and now works like a charm):
public class AuthorizeCheckOperationFilter : IOperationFilter
{
private readonly EndpointDataSource _endpointDataSource;
public AuthorizeCheckOperationFilter(EndpointDataSource endpointDataSource)
{
_endpointDataSource = endpointDataSource;
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var descriptor = _endpointDataSource.Endpoints.FirstOrDefault(x =>
x.Metadata.GetMetadata<ControllerActionDescriptor>() == context.ApiDescription.ActionDescriptor);
var hasAuthorize = descriptor.Metadata.GetMetadata<AuthorizeAttribute>()!=null;
var allowAnon = descriptor.Metadata.GetMetadata<AllowAnonymousAttribute>() != null;
if (!hasAuthorize || allowAnon) return;
operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" });
operation.Security = new List<OpenApiSecurityRequirement>
{
new()
{
[
new OpenApiSecurityScheme {Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "oauth2"}
}
] = new[] {"api1"}
}
};
}
}
The issue stated this:
ControllerActionDescriptor.EndpointMetadata only reflects the metadata
discovered on the controller action. Any metadata configured via the
endpoint APIs do not show up here. It was primarily the reason we
documented it as being infrastructure-only since it's a bit confusing
to use.
There's a couple of options you could use
a) You could decorate your controllers using [Authorize]. That should allow the metadata to show up in the property.
b) You could look up the metadata by reading from EndpointDataSource.

Confusion on ektron persona management

i have created a custom connection in dxh but that is not available on persona management site. from this video i understand that i have to create a class library for my custom connection. Is there any demo code available for this custom library?
i am stuck with the login management of dxh and persona, and how is this attributes working?
my code
using Attribute = Ektron.PersonaManagement.Common.Attribute;
public class ETPersona : PersonaAttributeProvider
{
public override IEnumerable<Attribute> GetAttributes()
{
return new List<Attribute>() {
new Attribute() {
DataSource = "test",
DataSourceId = "test",
Id = "test",
Name = new LocalizableText("test","test"),
Type =FieldType.String,
Options = new List<LocalizableText>(){new LocalizableText("test","test")}
}
};
}
public override UserAttributeData GetUserAttributeData(VisitorContext context)
{
return new UserAttributeData()
{
Attributes = new List<UserAttribute>() { new UserAttribute() { } },
HaveUserData = true,
UserKey = ""
};
}
}
Please see Ektron's forum discussion here for information:
http://developer.ektron.com/Forums/?v=t&t=1975

Custom OpenIdClient for Customer URL in MVC 4

I'm working with the default template for MVC 4 and trying to add my own openID provider for example http://steamcommunity.com/dev to the list of openID logins and an openID box where the user can type in their openID information.
To add Google I just un-comment
OAuthWebSecurity.RegisterGoogleClient();
as for other custom solutions you can do something like
OAuthWebSecurity.RegisterClient(new SteamClient(),"Steam",null);
The trouble I have is creating SteamClient (or a generic one) http://blogs.msdn.com/b/webdev/archive/2012/08/23/plugging-custom-oauth-openid-providers.aspx doesn't show anywhere to change the URL.
I think the reason I could not find the answer is that most people thought it was common sense. I prefer my sense to be uncommon.
public class OidCustomClient : OpenIdClient
{
public OidCustomClient() : base("Oid", "http://localhost:5004/") { }
}
Based on #Jeff's answer I created a class to handle Stack Exchange OpenID.
Register:
OAuthWebSecurity.RegisterClient(new StackExchangeOpenID());
Class:
public class StackExchangeOpenID : OpenIdClient
{
public StackExchangeOpenID()
: base("stackexchange", "https://openid.stackexchange.com")
{
}
protected override Dictionary<string, string> GetExtraData(IAuthenticationResponse response)
{
FetchResponse fetchResponse = response.GetExtension<FetchResponse>();
if (fetchResponse != null)
{
var extraData = new Dictionary<string, string>();
extraData.Add("email", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email));
extraData.Add("name", fetchResponse.GetAttributeValue(WellKnownAttributes.Name.FullName));
return extraData;
}
return null;
}
protected override void OnBeforeSendingAuthenticationRequest(IAuthenticationRequest request)
{
var fetchRequest = new FetchRequest();
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.FullName);
request.AddExtension(fetchRequest);
}
}
Retrieving extra data:
var result = OAuthWebSecurity.VerifyAuthentication();
result.ExtraData["email"];
result.ExtraData["name"];