I have a Razor pages application.
I am using MS Identity Platform Application. Now I need to acquire silently the tokes so I can call some APIs.
Everything works, but I have a portion of code is repeated for all pages:
[Microsoft.Identity.Web.AuthorizeForScopes(ScopeKeySection = "AdminApi:Scope")]
public class MyModel : PageModel
{
...
}
Exist a way to apply that attribute to the whole my application?
I have tryed something like that:
services.AddRazorPages(options =>
{
options.Conventions.AddAreaFolderApplicationModelConvention(
"MyArea", "/MyAreaFolder",
model => model.Filters.Add(new Microsoft.Identity.Web.AuthorizeForScopesAttribute() { ScopeKeySection = "AdminApi:Scope" }));
})
.AddMicrosoftIdentityUI();
I have also tryed using:
AddAreaFolderApplicationModelConvention
AddAreaPageApplicationModelConvention
AddPageApplicationModelConvention
No solution works. What can I try?
Thank you
Following the example at https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/blob/master/2-WebApp-graph-user/2-3-Multi-Tenant/Startup.cs it seems that you should be able to do just that with EnableTokenAcquisitionToCallDownstreamApi in the services configuration of your project
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
//Other parts of the authentication builder
.EnableTokenAcquisitionToCallDownstreamApi(
options =>
{
Configuration.Bind("AzureAd", options); //<-- use your own options
},
new string[] { Configuration["AdminApi:Scope"] } //<--Your scope(s)
)
//More parts of the authentication builder;
I haven't tested this solution, though.
Related
We are using fluentvalidation (with service stack) to validate our request DTO's. We have recently extended our framework to accept "PATCH" requests, which means we now have a requirement to apply validation ONLY when the patch contained the field being validated.
We have done this using an extension method such as this:
RuleFor(dto => dto.FirstName).Length(1,30)).WhenFieldInPatch((MyRequest dto)=>dto.FirstName);
RuleFor(dto => dto.MiddleName).Length(1,30)).WhenFieldInPatch((MyRequest dto)=>dto.MiddleName);
RuleFor(dto => dto.LastName).Length(1,30)).WhenFieldInPatch((MyRequest dto)=>dto.LastName);
This means we can run the same validation for a POST/PUT or a PATCH.
I have been looking for a way of hooking in to the fluent validation framework in such as way that we do not need to duplicate the .WhenFieldInPatch() rule on EVERY line in our validations, but have not yet found a nice way to do this.
I have tried the following:
Creating a helper method (in a in a base class) to intercept the initial "RuleFor" which adds the .When() clause up front, but the this does not work as fluent validation requires the .When() to be last
Intercepting the calls in PreValidation, but I can only intercept based on the whole class, and not on a rule by rule basis
Adding an extension method to apply to the end of every rule (as per example), but I cannot access the initial expression in order to check whether the field should be mapped - so I need to pass it in again.
Am I missing something, or am I attempting the impossible?
Thanks
When I need to share Fluent Validation Logic I'd use extension methods, here's an example of shared Extension methods for TechStacks, e.g:
public static class ValidatorUtils
{
public static bool IsValidUrl(string arg) => Uri.TryCreate(arg, UriKind.Absolute, out _);
public static string InvalidUrlMessage = "Invalid URL";
public static IRuleBuilderOptions<T, string> OptionalUrl<T>(
this IRuleBuilderInitial<T, string> propertyRule)
{
return propertyRule
.Length(0, UrlMaxLength)
.Must(IsValidUrl)
.When(x => !string.IsNullOrEmpty(x as string))
.WithMessage(InvalidUrlMessage);
}
}
And some examples where they're shared:
public class CreatePostValidator : AbstractValidator<CreatePost>
{
public CreatePostValidator()
{
RuleSet(ApplyTo.Post, () =>
{
RuleFor(x => x.Url).OptionalUrl();
});
}
}
public class UpdatePostValidator : AbstractValidator<UpdatePost>
{
public UpdatePostValidator()
{
RuleSet(ApplyTo.Put, () =>
{
RuleFor(x => x.Url).OptionalUrl();
});
}
}
What's the recommended way to register a SearchIndexClient into DI container? (let's say we only have a single index)
Register it as singleton or transient?
The short answer is that you should register it as a singleton, as long as you make sure to avoid using properties and methods that aren't thread-safe. Most of them are thread-safe; just avoid setting mutable properties from multiple threads and you should be fine.
For a more in-depth discussion of why this is the recommended practice and how you can extend it for more complex scenarios, see this related question.
In an ASP.NET Core app, you can inject the client as follows.
Install the packages: Microsoft.Extensions.Azure, Azure.Search.Documents (this is the latest)
In the ConfigureServices method in startup.cs, register the client:
using Microsoft.Extensions.Azure;
public void ConfigureServices(IServiceCollection services)
{
services.AddAzureClients(builder =>
{
builder.AddSearchIndexClient(new
Uri("my resource url"), new
AzureKeyCredential("my resource key"));
});
services.AddControllers();
}
Now say you want to use the client in your controller, you can inject it like so:
public class MyApiController : ControllerBase
{
private readonly SearchIndexClient _searchIndexClient;
public MyApiController(SearchIndexClient searchIndexClient)
{
_searchIndexClient = searchIndexClient;
}
}
You may not want to put your credentials directly in ConfigureServices though, in which case you could store them in appsettings.Development.json (or appsettings.Production.json):
"SearchDocument": {
"endpoint": "my resource url",
"credential": { "key": "my resource key" }
}
and do this in ConfigureServices:
services.AddAzureClients(builder =>
{
builder.AddSearchIndexClient(
Configuration.GetSection("SearchDocument"));
});
Read more about Dependency injection with the Azure SDK for .NET.
i'm try isolation ASP.NET Core Identity version 1.1.2, architecture DDD and creating a CrossCutting layer to create a classlib for aspnet core identity , and i using SimpleInjector 4.0.8 for my IoC, so i create a class ApplicationUserManager and ApplicationSignInManager, but i'm cannot register this class in the simlpleinjector container
ApplicationUserManager
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<ApplicationUser> passwordHasher,
IEnumerable<IUserValidator<ApplicationUser>> userValidators,
IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators,
ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<UserManager<ApplicationUser>> logger)
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
// My configurations stuffs were...
}
}
and the class ApplicationSignInManager
public class ApplicationSignInManager : SignInManager<ApplicationUser>
{
public ApplicationSignInManager(UserManager<ApplicationUser> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<ApplicationUser>> logger)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger)
{ }
// TODO: bug com tipo de retorno IdentityResult para ClaimsPrincipal
//public override Task<ClaimsPrincipal> CreateUserPrincipalAsync(ApplicationUser user)
//{
// return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
//}
}
and i try register this class in BootStrapper.cs like this
public static void RegisterServices(Container container)
{
// Infra.Data App Context
// IdentityAppDbContext
container.RegisterSingleton(() =>
{
var options = new DbContextOptions<IdentityAppDbContext>();
return new IdentityAppDbContext(options);
});
// NetCore Identity
container.RegisterSingleton<ApplicationUserManager>();
container.RegisterSingleton<ApplicationSignInManager>();
container.RegisterSingleton<IUserStore<ApplicationUser>>(() =>
{
var options = new DbContextOptions<IdentityAppDbContext>();
return new UserStore<ApplicationUser>(new IdentityAppDbContext(options));
});
container.Register(() => (IOptions<IdentityOptions>)new IdentityOptions());
container.RegisterSingleton<IPasswordHasher<ApplicationUser>>(() => new PasswordHasher<ApplicationUser>());
}
but when i run the aaplication return erros says i need registre IOptions, IPasswordHasher and other params in contructors class, the queston is, how can'i register this class?
I had a similar requirement: my application needs to interact with identity (it's basically about human resources, that might have a user account on their own or not), and after struggling a long time with with various issues having identity in the framework container, I was trying to configure simple injector to provide all identity services. Call it crazy, but this is how it works (SimpleInjector 4.0 and ASP.Net Identity Core 2.0.1):
This is the "ConfigureServices" part:
// identity options are provided from outside, allowing configuration of the framework
container.RegisterSingleton<IOptions<IdentityOptions>>(new OptionsWrapper<IdentityOptions>(identityOptions));
// we rely on BCrypt instead of the default PBKDF2 hashing algorithm
container.Register<IPasswordHasher<MepUser>>(()=>new BCryptPasswordHasher(bcryptOptions));
// forwarding the framework logger to our own logging framework
container.Register<Microsoft.Extensions.Logging.ILoggerFactory, FrameworkToBackendFxLoggerFactory>();
container.Register(typeof(Microsoft.Extensions.Logging.ILogger<>), typeof(Microsoft.Extensions.Logging.Logger<>));
// identity store = a specific Entity Framework Core DbContext, getting mapped into a specific db scheme
container.RegisterSingleton(identityDbContextOptions);
container.Register<MepIdentityDbContext>();
// UserStore<T> und RoleStore<T> both require a DbContext (no IdentityDbContext, neither a generic TDbContext)
// via constructor, but the container only knows about MepIdentityDbContext so we have to wire it manually
container.Register<IUserStore<MepUser>>(() => new UserStore<MepUser>(
container.GetInstance<MepIdentityDbContext>(),
container.GetInstance<IdentityErrorDescriber>()));
container.Register<IRoleStore<IdentityRole>>(() => new RoleStore<IdentityRole>(
container.GetInstance<MepIdentityDbContext>(),
container.GetInstance<IdentityErrorDescriber>()));
// framework internal services
container.Register<IdentityErrorDescriber>();
container.Register<ILookupNormalizer, UpperInvariantLookupNormalizer>();
container.Register<IPasswordValidator<MepUser>, PasswordValidator<MepUser>>();
container.Register<IUserValidator<MepUser>, UserValidator<MepUser>>();
container.Register<IUserClaimsPrincipalFactory<MepUser>, UserClaimsPrincipalFactory<MepUser>>();
container.Register<IRoleValidator<IdentityRole>, RoleValidator<IdentityRole>>();
// ASP.Net Core Identity violates a design decision of SimpleInjector: The API clearly differentiates the registration of collections
// https://simpleinjector.readthedocs.io/en/latest/decisions.html#the-api-clearly-differentiates-the-registration-of-collections
// By registering IEnumerables of those violating services with a simple wrapping single item array, Identity is happy
container.Register<IEnumerable<IUserValidator<MepUser>>>(() => new[] { container.GetInstance<IUserValidator<MepUser>>() });
container.Register<IEnumerable<IRoleValidator<IdentityRole>>>(() => new[] { container.GetInstance<IRoleValidator<IdentityRole>>() });
container.Register<IEnumerable<IPasswordValidator<MepUser>>>(() => new[] { container.GetInstance<IPasswordValidator<MepUser>>() });
// Role and UserManager reflect the API surface of the whole ASP.Net Core Identity framework
container.Register<RoleManager<IdentityRole>>();
// UserManagerShim is omitting (=nulling) the IServiceProvider parameter of UserManager<T>
container.Register<UserManager<MepUser>, UserManagerShim>();
and this needs to be done during "Configure", otherwise password reset and other two factor token stuff won't work (runtime error, although the container was verified)
// if you eagerly instantiate a provider instance that is considered being a singleton and
// put it into the respective field in the TokenProviderDescriptor and list it in the option's
// provider map, ASP.Net Core Identity will use this one instead of asking the IServiceProvider
// instance injected into UserManager<T> (that we do not do, because it is bad design. Instead,
// we just stuff null in there)
identityOptions.Tokens.ProviderMap[TokenOptions.DefaultProvider] =
new TokenProviderDescriptor(typeof(DataProtectorTokenProvider<MepUser>))
{
ProviderInstance = new DataProtectorTokenProvider<MepUser>(
dataProtectionProvider,
new OptionsWrapper<DataProtectionTokenProviderOptions>(new DataProtectionTokenProviderOptions()))
};
identityOptions.Tokens.ProviderMap[TokenOptions.DefaultEmailProvider] =
new TokenProviderDescriptor(typeof(EmailTokenProvider<MepUser>)) {
ProviderInstance = new EmailTokenProvider<MepUser>()
};
Although there appears AspNetCore in the namespace definition, nothing is dependent on ASP.Net Core hosting, actually. The only dependency to the "outside world" are the options (POCOs) and an implementation of IDataProtectionProvider, that will use the KeyRing in ASP.Net scenarios, but can also be satisfied using the EphemeralDataProtectionProvider in tests.
Caveat: SignInManager<T> is not being injected. This class is a total mess, depending on the whole world, so I rewrote it basically matching my requirements. You also loose lots of the configuration flexibility regarding token providers. But however, in my case it's something you decide once at design time, so I am fine with it.
I am using Breeze with much success in my SPA, but seem to be stuck when trying to return parent->child data in a single query by using expand().
When doing a single table query, the $type in the JSON return is correct:
$type: MySPA.Models.Challenge, MySPA
However if I use expand() in my query I get the relational data, but the $type is this:
System.Collections.Generic.Dictionary 2[[System.String, mscorlib],[System.Object, mscorlib]]
Because of the $type is not the proper table + namespace, the client side code can't tell that this is an entity and exposes it as JSON and not a Breeze object (with observables, entityAspect, etc.).
At first I was using my own ContextProvider so that I could override the Before/After saving methods. When I had these problems, I reverted back to the stock EFContextProvider<>.
I am using EF5 in a database first mode.
Here's my controller code:
[BreezeController]
public class DataController : ApiController
{
// readonly ModelProvider _contextProvider = new ModelProvider();
readonly EFContextProvider<TestEntities> _contextProvider = new EFContextProvider<TestEntities>();
[HttpGet]
public string Metadata()
{
return _contextProvider.Metadata();
}
[Queryable(AllowedQueryOptions = AllowedQueryOptions.All)]
[HttpGet]
public IQueryable<Challenge> Challenges()
{
return _contextProvider.Context.Challenges;
}
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
return _contextProvider.SaveChanges(saveBundle);
}
public IQueryable<ChallengeNote> ChallengeNotes()
{
return _contextProvider.Context.ChallengeNotes;
}
}
Here's my BreezeWebApiConfig.cs
public static void RegisterBreezePreStart()
{
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "BreezeApi",
routeTemplate: "breeze/{controller}/{action}"
);
}
Is there a configuration setting that I am missing?
Did you try "expanding" on server side? Is it needed to do expand on client side? I tried to do expand before but failed for me as well, did some research and decided I'd rather place it on server:
[HttpGet]
public IQueryable<Challenge> ChallengesWithNotes()
{
return _contextProvider.Context.Challenges.Include("ChallengeNotes");
}
This should be parsed as expected. On client side you would query for "ChallengeNotes" instead of "Challenges" and you wouldn't need to write expand part.
I strongly suspect that the problem is due to your use of the [Queryable] attribute.
You must use the [BreezeQueryable] attribute instead!
See the documentation on limiting queries.
We are aware that Web API's QueryableAttribute has been deprecated in favor of EnableQueryAttribute in Web API v.1.5. Please stick with BreezeQueryable until we've had a chance to write a corresponding derived attribute for EnableQuery. Check with the documentation for the status of this development.
In routes I have
Router::connect('/opauth-complete/*', array('controller' => 'app_users', 'action' => 'opauth_complete'));
If I change pointer to controller app_users with anything else and create controller everything works with no error. But I need it to work with AppUsersController.
AppUsersController looks like this
App::uses('UsersController', 'Users.Controller');
class AppUsersController extends UsersController {
public function beforeFilter() {
parent::beforeFilter();
$this->User = ClassRegistry::init('AppUser');
}
// ...
// ...
public function opauth_complete() {
die(1);
}
// ...
// ...
}
So, plugin is CakeDC Users and another plugin that goes to /example/callback after /example/auth/facebook is Opauth plugin.
Error message looks like this
The request has been black-holed
Error: The requested address '/example/opauth-complete' was not found on this server.
This is perfectly possible to make these two plugins work together; when browser points to /example/auth/facebook, it redirects to /example/auth/callback and somehow it needs opauth-complete route to link to specific method.
All works if not pointed to app_users that extends plugin, uses plugin. Does not work only with this case. How can users of these two plugins get around such situation.
I solved it by disabling Security component on Opauth action in my AppUsersController. Thing is that Opauth transfers data using POST and you should either change a method of it (ie: use Sessions, GET) or disable Security component.
For a method change use this in your bootstrap.php or core.php
Configure::write('Opauth.callback_transport', 'session'); // you can try 'get' too
To follow my approach add this to a controller where error occurs and where you place your opauth_complete method
public function beforeFilter() {
// ...
if (isset($this->Security) && $this->action == 'opauth_complete') {
$this->Security->validatePost = false;
$this->Security->csrfCheck = false;
}
// ...
}
P.S. Changing method to Sessions has its drawbacks, you can take a look at comments here at Github Opauth issue #16