Giter VIP home page Giter VIP logo

jonpsmith / authpermissions.aspnetcore Goto Github PK

View Code? Open in Web Editor NEW
745.0 32.0 150.0 5.6 MB

This library provides extra authorization and multi-tenant features to an ASP.NET Core application.

Home Page: https://www.thereformedprogrammer.net/finally-a-library-that-improves-role-authorization-in-asp-net-core/

License: MIT License

HTML 14.48% C# 85.07% CSS 0.28% JavaScript 0.16%
asp-net-core authorization jwt-token multi-tenant aspnetcore saas

authpermissions.aspnetcore's People

Contributors

akema-trebla avatar emorell96 avatar idan-h avatar jonpsmith avatar mohamed-azhar avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

authpermissions.aspnetcore's Issues

.net 7 Error Sharding Example

Hi Jon, I'm trying to add your Sharding Example to my .net 7 Web App. I'm getting the following error: I can't seem to locate the issue.

System.AggregateException
HResult=0x80131500
Message=Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: AuthPermissions.AdminCode.ITenantChangeService Lifetime: Transient ImplementationType: MyProject.Web.Sharding.ShardingTenantChangeService': Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions1[MyProject.Web.Sharding.ShardingSingleDbContext]' while attempting to activate 'MyProject.Web.Sharding.ShardingTenantChangeService'.) Source=Microsoft.Extensions.DependencyInjection StackTrace: at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection1 serviceDescriptors, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
at Microsoft.Extensions.Hosting.HostApplicationBuilder.Build()
at Microsoft.AspNetCore.Builder.WebApplicationBuilder.Build()
at Program.

$(String[] args) in C:\MyProject\src\MyProject.Web\Program.cs:line 84

This exception was originally thrown at this call stack:
[External Code]

Inner Exception 1:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: AuthPermissions.AdminCode.ITenantChangeService Lifetime: Transient ImplementationType: MyProject.Web.Sharding.ShardingTenantChangeService': Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions`1[MyProject.Web.Sharding.ShardingSingleDbContext]' while attempting to activate 'MyProject.Web.Sharding.ShardingTenantChangeService'.

Inner Exception 2:
InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions`1[MyProject.Web.Sharding.ShardingSingleDbContext]' while attempting to activate 'MyProject.Web.Sharding.ShardingTenantChangeService'.

This is my startup code. It errors on the builder.Build() line

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext(options =>
options.UseSqlServer(connectionString));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity(options =>
options.SignIn.RequireConfirmedAccount = false)
.AddEntityFrameworkStores();

builder.Services.AddControllersWithViews()
.AddRazorRuntimeCompilation();

builder.Services.RegisterAuthPermissions(options =>
{
options.TenantType = TenantTypes.SingleLevel | TenantTypes.AddSharding;
options.EncryptionKey = builder.Configuration[nameof(AuthPermissionsOptions.EncryptionKey)];
options.PathToFolderToLock = builder.Environment.WebRootPath;
options.SecondPartOfShardingFile = builder.Environment.EnvironmentName;
options.Configuration = builder.Configuration;
})
//NOTE: This uses the same database as the individual accounts DB
.UsingEfCoreSqlServer(connectionString)
.IndividualAccountsAuthentication()
.RegisterAddClaimToUser()
.RegisterAddClaimToUser()
.RegisterTenantChangeService()
.AddRolesPermissionsIfEmpty(MyProjectAppAuthSetupData.RolesDefinition)
.AddTenantsIfEmpty(MyProjectAppAuthSetupData.TenantDefinition)
.AddAuthUsersIfEmpty(MyProjectAppAuthSetupData.UsersRolesDefinition)
.RegisterFindUserInfoService()
.RegisterAuthenticationProviderReader()
.AddSuperUserToIndividualAccounts()
.SetupAspNetCoreAndDatabase(options =>
{
//Migrate individual account database
options.RegisterServiceToRunInJob<StartupServiceMigrateAnyDbContext>();
//Add demo users to the database (if no individual account exist)
options.RegisterServiceToRunInJob();

    //Migrate the application part of the database
    options.RegisterServiceToRunInJob<StartupServiceMigrateAnyDbContext<ShardingSingleDbContext>>();
    //This seeds the invoice database (if empty)
    options.RegisterServiceToRunInJob<StartupServiceSeedShardingDbContext>();
});

//This is used to set a tenant as "Down",
builder.Services.AddDistributedFileStoreCache(options =>
{
options.WhichVersion = FileStoreCacheVersions.Class;
//I override the the default first part of the FileStore cache file because there are many example apps in this repo
options.FirstPartOfCacheFileName = "MyProjectCacheFileStore";
}, builder.Environment);

//manually add services from the AuthPermissions.SupportCode project
builder.Services.AddSingleton<IGlobalChangeTimeService, GlobalChangeTimeService>(); //used for "update claims on a change" feature
builder.Services.AddSingleton<IDatabaseStateChangeEvent, TenantKeyOrShardChangeService>(); //triggers the "update claims on a change" feature
builder.Services.AddTransient<IAccessDatabaseInformation, AccessDatabaseInformation>();
builder.Services.AddTransient<ISetRemoveStatus, SetRemoveStatus>();

var app = builder.Build();

Refreshing JWT adds duplicate audiences to token

The current implementation of getting the claims for the new token from the ClaimsPricipal of the expired token, creates duplicate audiences and thus fails after the first token refresh.

Recalculating the claims based on the userId from the ClaimsPrincipal resolves this issue.

Exception using SignInManager

I am getting the following exception when attempting to login a user with email and password:

System.ArgumentException: Expression of type 'System.Threading.Tasks.Task`1[System.Collections.Generic.List`1[AuthPermissions.DataLayer.Classes.RoleToPermissions]]' cannot be used for return type 'System.Threading.Tasks.Task`1[System.Collections.Generic.IEnumerable`1[AuthPermissions.DataLayer.Classes.RoleToPermissions]]'
   at System.Linq.Expressions.Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection`1 parameters, String paramName)
   at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable`1 parameters)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)

The login code is as follows:

var result = await _signInManager.PasswordSignInAsync(request.Email, request.Password, false, false);
    
if (result.Succeeded)
{
  var user = await _userManager.FindByEmailAsync(request.Email);
  var token = await _tokenBuilder.GenerateTokenAndRefreshTokenAsync(user.Id);

  return Ok(new UserLoginResponse(token.Token, token.RefreshToken));
}
else
{
  return BadRequest(new {Message = "Email or password is incorrect"});
}

Improvement to the multi-tenant ITenantChangeService to better support sharding

An application using AuthP's multi-tenant feature requires a service that implements the ITenantChangeService which is called when a tenant is created, updated, moved or deleted. The current implementation creates a transaction that covers both the AuthP DbContext and the application's DbContext.

This works, but has the following limitations:

  • The AuthP DbContext and the application's DbContext must use the same database. This rules out the sharding approach to building multi-tenant applications.
  • The ITenantChangeService is quite complex for the user - it would be nice to make it simpler. One approach would have an abstract class containing a default creation of the application's DbContext, with a way to override that default creation of the DbContext if the developer needs to add extra features.

Possible solution

Looking at the code it could be changed to a transaction on just the AuthP DbContext, and within that transaction a call to the (modified) ITenantChangeService. If the ChangeService fails / returns an error it can roll back the AuthP changes.

For some changes, like delete and move, the ITenantChangeService would need a transaction too. In this case it gets complex.

Things to look at

AuthP Assuming Cookie-Based IdentityUser

While AuthP can be used with any authentication provider by implementing a custom ISyncAuthenticationUsers , when executing the finalise methods, such as SetupAspNetCoreAndDatabase it assumes the application is using cookies and IdentityUser which makes it unsuitable for services like Azure AD and B2C and causes the following exception.

System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory1[Microsoft.AspNetCore.Identity.IdentityUser] Lifetime: Scoped ImplementationType: AuthPermissions.AspNetCore.Services.AddPermissionsToUserClaims': Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager1[Microsoft.AspNetCore.Identity.IdentityUser]' while attempting to activate 'AuthPermissions.AspNetCore.Services.AddPermissionsToUserClaims'.)'`

Example 3 App Auth Setup Data - string not formatted Correctly

I am trying to run the repo, but for some reason, it's not adding any user to the AuthUsers Table.

Error:

An error occurred while starting the application.
AuthPermissionsBadDataException: True:
Line/index 2, char: 1: The role App Admin wasn't found in the auth database.
App Admin
|
Line/index 1: The user [email protected] didn't have any roles.
App Admin
Line/index 3, char: 1: The role Tenant User wasn't found in the auth database.
Tenant User
|
Line/index 2: The user [email protected] didn't have any roles.
Tenant User
Line/index 4, char: 1: The role Tenant Admin wasn't found in the auth database.
Tenant Admin,Tenant User
|
Line/index 4, char: 14: The role Tenant User wasn't found in the auth database.
Tenant Admin,Tenant User
|
Line/index 3: The user [email protected] didn't have any roles.
Tenant Admin,Tenant User
Line/index 5, char: 1: The role Tenant User wasn't found in the auth database.
Tenant User
|
Line/index 4: The user [email protected] didn't have any roles.
Tenant User
Line/index 6, char: 1: The role Tenant User wasn't found in the auth database.
Tenant User
|
Line/index 5: The user [email protected] didn't have any roles.
Tenant User
Line/index 7, char: 1: The role Tenant User wasn't found in the auth database.
Tenant User
|
Line/index 6: The user [email protected] didn't have any roles.
Tenant User
Line/index 8, char: 1: The role Tenant User wasn't found in the auth database.
Tenant User
|
Line/index 7: The user [email protected] didn't have any roles.
Tenant User
AuthPermissions.CommonCode.ErrorReportingExtensions.IfErrorsTurnToException(IStatusGeneric status) in ErrorReportingExtensions.cs, line 63

AuthPermissionsBadDataException: True: Line/index 2, char: 1: The role App Admin wasn't found in the auth database. App Admin | Line/index 1: The user [email protected] didn't have any roles. App Admin Line/index 3, char: 1: The role Tenant User wasn't found in the auth database. Tenant User | Line/index 2: The user [email protected] didn't have any roles. Tenant User Line/index 4, char: 1: The role Tenant Admin wasn't found in the auth database. Tenant Admin,Tenant User | Line/index 4, char: 14: The role Tenant User wasn't found in the auth database. Tenant Admin,Tenant User | Line/index 3: The user [email protected] didn't have any roles. Tenant Admin,Tenant User Line/index 5, char: 1: The role Tenant User wasn't found in the auth database. Tenant User | Line/index 4: The user [email protected] didn't have any roles. Tenant User Line/index 6, char: 1: The role Tenant User wasn't found in the auth database. Tenant User | Line/index 5: The user [email protected] didn't have any roles. Tenant User Line/index 7, char: 1: The role Tenant User wasn't found in the auth database. Tenant User | Line/index 6: The user [email protected] didn't have any roles. Tenant User Line/index 8, char: 1: The role Tenant User wasn't found in the auth database. Tenant User | Line/index 7: The user [email protected] didn't have any roles. Tenant User
AuthPermissions.CommonCode.ErrorReportingExtensions.IfErrorsTurnToException(IStatusGeneric status) in ErrorReportingExtensions.cs
+
throw new AuthPermissionsBadDataException(status.Errors.Count() == 1
AuthPermissions.AspNetCore.HostedServices.AddRolesTenantsUsersIfEmptyOnStartup.StartAsync(CancellationToken cancellationToken) in AddRolesTenantsUsersIfEmptyOnStartup.cs
+
status.IfErrorsTurnToException();
Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
Example3.MvcWebApp.IndividualAccounts.Program.Main(string[] args) in Program.cs
+
CreateHostBuilder(args).Build().Run();

Code for minimal API's fluent HasPermission

Hey, I wrote small piece of code to handle the HasPermission attribute fluently for minimal api.

Usage:

app.MapGet("/", () => "Hello World!")
            .HasPermission(ApplicationPermission.AccessAll);

Extension method:

public static TBuilder HasPermission<TBuilder>(this TBuilder builder, object permission) where TBuilder : IEndpointConventionBuilder
  {
      var authorizeData = new HasPermissionAttribute(permission);
      builder.RequireAuthorization(authorizeData);

      return builder;
  }

Which namespace you would like this method to go into? I think people who use minimal api might find it useful

SigningKey Property on AuthPJwtConfiguration doesn't support keys generated as byte[]

In my project I have a requirement to use token signing keys that have either been generated per instance of the web server or by loading a key from a certificate. Both of these scenarios produce a key that is stored in a byte[].

I have had issue encoding that byte[] into a string in a useable way, the resulting key in the token builder doesn't match the original byte[] given to the token validation parameters.

Based on the examples and the documentation I understand the intention is to load a key from a file or some sort of secrets store, and that the key is some sort of human readable string. This doesn't work when using keys generated from crypto classes like System.Security.Cryptography.Aes which produce bytes that don't encode into strings in a reversible way.

My suggestion for resolving this which wouldn't break the current API would be to add a "SigningKeyRaw" property of type byte[] to the AuthPJwtConfiguration class. This property can be used directly by the token builder and the current "SigningKey" property can be updated to encode and decode to the new property.

I can raise a PR with this change if that is convenient.

Apply Migration based on Database Provider

Hi @JonPSmith

I've added PostgreSQL database provider in my fork however I had to delete the migrations classes from the DataLayer folder, change the DB provider in DesignTimeContextFactory class and generate new ones. I was wondering on how to do this dynamically. I was thinking to generate migrations for PostgreSQL under a different folder and in SetupExtensions class apply them based on the database provider. For ex in UsingEfCoreSqlServer() apply the migration generated for SqlServer. Is that possible? Or how would you apply the migration based on the database provider?

Thanks!

Hierarchical multi-tenant - problem when using the Move feature means some logged-in users will have the wrong DataKey

There is a problem when using the hierarchical Move feature with the current arrangement. The problem comes that logged-in users that are linked to tenants that have been moved will have the wrong DataKey. This could cause lots of problems.

The best solution is remove the DataKey claim and replace it with the tenant primary key (tenantId) in the claims. The tenantId doesn’t change with a move and you then use the tenantId to get the DataKey. The down side is getting the DataKey adds an extra database access to get the Parentkey from the tenant, which when combined with the tenantId will create the correct DataKey. To do this you would create a different IGetDataKeyFromUser which contains a lazy DataKey which accesses the AuthP tenant to get the DataKey.

NOTE: This is a breaking change, and needs a way to transition an already running application

Some claim changes ignored until logout/login

I've implemented the RoleChangedDetectorService and if I change the roles assigned to a user it seems to take immediate effect which is great. However, if I edit the permissions enabled in a role this doesn't seem to trigger an update to logged-in users claims.
It seems to identify users that will be impacted (i.e. users belonging to that role) but it doesn't actually cause their claims to be updated (until the user logs out and back in). Am I missing something?

Additionally, if I edit the tenant a user belongs to, the user's data key in their claim does not get updated (until logout/login).
Is this by design? The TenantKeyOrShardChangeService only looks to detect changes to the Tenant Hierarchy, not changes to an individual user's tenant.

I've implemented the various change services as per your examples, so my understanding is it shouldn't be necessary for the user to logout and log back in again for changes to their account to become effective. Specifically I've implemented: GlobalChangeTimeService, SomethingChangedCookieEvent, RoleChangedDetectorService and TenantKeyOrShardChangeService.

Thanks in advance and apologies if I'm missing something obvious.

Multi-tenant Roles: Problem when changing a Roles RoleType or when deleting

With the new multi-tenant Role types (see this explanation about multi-tenant Role types in the docs) in version 2.0.0 are two issues that haven't been covered in the current release (2.1.0). They are

1. Changing the RoleType can cause problems

The issues are

  1. If a Normal Role is changed to a HiddenFromTenant then that Role should be removed from any tenant users.
  2. If a Normal or HiddenFromTenant Role's RoleType are changed to TenantAutoAdd or TenantAdminAdd then the roles are in the correct place (i.e. should be in the TenantRoles)
  3. If a TenantAutoAdd or TenantAdminAdd Role's RoleType are changed to Normal or HiddenFromTenant then the roles are in the correct place (i.e. should be in the AuthUser's Roles)

I could just detect these changes and sent back an error, but issue 1 (Normal Role is changed to a HiddenFromTenant) would be fairly easy to delete that Role from Tenant users using the Role Delete checks.

Delete Role

The DeleteRoleAsync method works on tenant Roles because it deletes the RoleToPermissions which remove the user or tenant link. The only problem is that the QueryUsersUsingThisRole method only covers AuthUsers. We need a QueryTenantsUsingThisRole too.

Can this library work with microservice based solution

Hi
This is not a bug. i just want to verify one use-case. I have 3-4 services and a single authentication service. I need multi-tenancy support in all of them. Will this library be suitable in this scenario and where i can find examples or blogs related to this. Thanks.

Changes needed to the sharding feature to work in production

Version 3.0.0 introduced a sharding approach which allows a multi-tenant application to use multiple databases to place tenant data into. The version 3.0.0 used the "ConnectionStrings" section of the appsettings file, using the names of the connection strings, e.g. "DefaultConnection" to pick the database.

However, when I tried to run the sharding example app on Azure I found some issues that would make updating the database information while the application was running pretty difficult. These issues have made me change the way of handing multiple databases, and especially how you would add (or remove) databases in production. This issue explains the problem and then describes the solution (which will be in version 3.2.0).

The Problem

Connection strings contain secrets, e.g. user name and password for the database server. While can define the connection strings when you deploy, then recommended way in Azure is to set up the connection strings in the Azure App Service via the Settings -> Configuration -> Connection strings feature.

There is a way to add/change a connection string via an API in Azure, but that only works for Azure and I want the library to be used in any web server, e.g. AWS, Docker etc.

The assumptions / rules

So, I assume any web server / database server will have a way to hold the database connection string in a private way. I also assume that if you are going to use multiple databases (for a sharding multi-tenant application) that one database server will have multiple databases. But I also allow for multiple database servers, say if you want some servers / database geographically spread.

The solution

  1. I use the "ConnectionStrings" section of the ASP.NET Core appsettings file to hold the private information for a database server. Azure has a good approach for that.
  2. I create a second section called "ShardingData" in an extra appsettings file which holds a array with each database that can be used by the sharding multi-tenant app - known as database information. The information for a database has the following information:
    • Name: This is a reference to this database information. This name is held in the Tenant information and ends up in a claim.
    • ConnectionName: This contains the name of the connection string the the "ConnectionStrings" section mentions in 1.
    • DatabaseName: This holds the name of the database. If null, then it uses the database in the connection string.
    • DatabaseType: This holds the database type, e.g. SqlServer, Postgres, etc.

So when a user that it linked to a tenant logs in, then the Name of the database information is added to their claims. When the user wants to access the data in their parts of the database, then the following steps are taken:

  1. The Name claim is used to find the database information in the configuration
  2. It then gets the connection string from the "ConnectionStrings" section
  3. Then using the correct SQL connection string builder (based on the DatabaseType) to create a connection string with the database server information form the "ConnectionStrings", but with the database name set from the DatabaseName parameter in the database information.

All of these steps are handled by the IShardingConnections service. It is possible that someone's application might need something changed, so I have added a way to replace the default ShardingConnections code. NOTE: Until version 3.2.0 is out the links to IShardingConnections and ShardingConnections are using the version 3.0.0 code.

Implementation specifics

  • I use IOptionsSnapshot<T> to access the "ConnectionStrings" and "ShardingData" sections. This means if the data is changed, then the latest data is used. The IOptionsSnapshot<T> method is very fast.
  • I create a separate file called shardingsettings.json and registered to ASP.NET Core's Configuration. Having its own file has two benefits:
    • You want the shardingsettings.json file in production created by some admin code, and you definitely don't it overwritten when you deploy an updated version of your application - see NOTE1 and NOTE2 for how you do this.
    • It makes it much easier to update the file because it only contains the database infomation.

NOTE1: You need to set the shardingsettings.json file properties shown below to stop the file from sent to the web server when you deploy your application:

  • Build Action to "None"
  • Copy to Output Directory to "Do not copy"

NOTE2: If no shardingsettings.json is found, then it sets up a default database, which is linked directly to the "DefaultConnection" connection string.

Improvement to `ShardingConnections.GetDatabaseInfoNamesWithTenantNamesAsync` method

The GetDatabaseInfoNamesWithTenantNamesAsync method returns a list of the names of the databases found in the shardingsettings.json file, with information on what tenants are in each database. This is useful when a tenant is created or moved as you need to select a database to hold the new tenant.

In version 3.2.0 of this library the GetDatabaseInfoNamesWithTenantNamesAsync method returned a list where each entry contains:

  • The database information name
  • A list of tenant name(s) stored in the database linked to the database information name

In version 3.3.0 of this library the GetDatabaseInfoNamesWithTenantNamesAsync method a third part is provided, which means the returned information is

  • The database information name
  • A bool? value called HasOwnDb, which provides a value that tells you:
    • null: The database is empty
    • true: There is one sharding tenant in this database
    • false: The database contains tenants that can shares a database - see NOTE1
  • A list of tenant name(s) stored in the database linked to the database information name

NOTE1: If database information name matches the ShardingDefaultDatabaseInfoName held in the AuthPermissionsOptions (default value = "Default Database") then even if there no tenants the HasOwnDb will be false, as that database contains the AuthP data, so its not applicable for sharding tenants.

This extra data is useful for a admin user, but the real reason of this change is because of the new GetDatabaseForNewTenant service / method that can automatically select a database for a new tenant.

Critical bug: hierarchical multi-tenant applications to sometimes access another tenant’s data.

While working on Version 2 of this library I found a bug in the use of the DataKey which can cause hierarchical multi-tenant applications to sometimes access another tenant’s data. This is therefore a critical bug.

This bug is fixed in AuthPermissions.AspNetCore 2.0.0.

BUT if you built a single or hierarchical multi-tenant application using Version 1 of this library, then you need to migrate your application's databases that use the DataKey. This section in the [Migrate from AuthPermissions.AspNetCore 1.* to 2.0] document explains how to do this.

NOTE: Single multi-tenant applications in version 1 didn’t have a bug, but the AuthP DataKey uses the same DataKey for Single and Hierarchical multi-tenant, so you still need to follow this information when upgrading to Version 2.

Logical problem with IDisableJwtRefreshToken

Hey, I have a logical problem with IDisableJwtRefreshToken.MarkJwtRefreshTokenAsUsedAsync.
Lets say a user logs in, and the access token is valid for 30 minutes. Then, after 10 minutes the user logs in from another machine and creates another refresh token.
Now, he logs out of the first login, and instead of disabling the first login refresh token, the second login refresh token gets disabled. The result is that he "logged out" on both machines unintendedly.
It is also a minor security vulnerability, because someone can utilize the first refresh token (it is deleted from the client side but still valid and able to produce new refresh tokens).

AuthP support for UserNames?

If you try to add a UserName to a User in Example3AppAuthSetupData.UsersRolesDefinition, you'll get an exception during the AuthDBContext migration as follows...

"AuthPermissions.BaseCode.CommonCode.AuthPermissionsBadDataException: 'Index X: The user XXXXX didn't have a userId and the IFindUserInfoService couldn't find it either.
XXXXX'"

It appears that the AuthP code is creating IdentityUsers with UserName defaulting to Email but allowing a UserName to be specified for AuthP users in the Bulk Load facility.

As a side issue, the password for new users defaults to the user's email address. Would be better to be able to set a default password from dev settings or in production randomize the initial password and force reset on initial sign-on.

Changing out IdentityUser

Is there any way to change out the default IdentityUser class with a custom class? When I use IdentityDbContext it crashes because the library expects it to be IdentityDbContext in IndividualAccountsAddSuperUser.


Christopher

Invoke app-defined ITenantChangeService from BulkLoadTenantsService.AddTenantsToDatabaseAsync

The app-defined ITenantChangeService is not invoked during the bulk-loading process (from BulkLoadTenantsService.AddTenantsToDatabaseAsync). This leaves the app-specific database (CompanyTenant in Example 3) in an inconsistent state after new tenants are added via the API.

This may be by-design for reasons I don't fully understand. If so, please close this issue. Otherwise, I am happy to pull a PR to address this issue.

Invite user that already invited on another tenant by same email Id.

Hi Jon,
I want to know that is it possible to do same email id can be user of another tenant.
Like some application, I seen, Admin of App can invite user which is already somewhere invited or Tenant.

In AuthPermission Project, When I am trying to add it will not allow because email Id already present in AspNetUsers table.

Any suggestion on this, sir?

Create a demo using JWT and Cookie both

I haven't found any good tutorial that demonstrate how to use both JWT and Cookie based authentication in a standard way.

A demo project with detailed article would be great.

Question about the design

Hey, I have a question;
Why not extend microsoft's identity framework, instead of managing multiple tables in parallel?

(Ex. dbo.AspNetUsers - authp.AuthUsers)

You can use a custom user, add the roles etc, all in the same context, and then add the tenants and required extra tables and services

It makes the need of sync between those tables unnecessary, and the db much more normalized and less complex.

Great work btw,
Thanks!

The updating of a user's tenant in a multi-tenant application needs work, as the Roles might change.

If an app admin creates a new tenant user or update an existing tenant user it won't show the correct Roles when use the user admin method called GetRoleNamesForUsersAsync. The GetRoleNamesForUsersAsync method takes in the user's userId. This has two problems:

  • When creating a user you won't get the correct tenant users because the tenant isn't set.
  • If you change the tenant that a user is linked to, then the Roles might not be correct because the tenant Roles in the new tenant can be different to the user's original tenant Roles. That also includes going from no tenant to a tenant - see issue #13.

Suggested solution

  • Change create to check that the Roles are correct for the applied tenant. Check if any of the Roles provided aren't correct for the tenant then sent back an error for each Role.
  • For Update, do the same as create - return errors if Roles aren't correct for the the tenant.

Version 2: Multi-tenant, Access the data of another tenant user

In the original multi-tenant application I designed the App Admin (i.e. a user manages the whole application, not linked to tenant) needed to access the data that a tenant user to understand / fix an tenant user's data.

I will add a "Access data as another tenant user" feature which will override the DataKey of the current user with the DataKey from the selected tenant user. This provides a App Admin or an Tenant Admin to access the user's data, plus a feature useful in hierarchical multi-tenant applications.

The first thing is to find the tenant user that you want to access their data. This will use the current List AuthP's Users. The list user already has the ability to only return the users that the current user can see, e.g. App Admin can see all user, Tenant Users can only see the users in their tenant.

Once a user has been found you will call the StartAccessToUserDataAsync method with your found user. This will create a Cookie that will contain the selected user's DataKey which will override the default DataKey of the current user. This Cookie should also show a prominent display showing you are in this mode. The StopAccessToUserDataAsync method will remove the Cookie and return to normal usage.

There are two usages of this feature:

  • For the App Admin / customer support users to access a tenant data in order to find / fix a problem reported for a tenant user.
  • In a hierarchical multi-tenant application user in a higher level, say a user in the tenant "West Coast" (see hierarchical example) can directly access a lower-level user's data, say the "SanFran shop1". This is especially useful if the "West Coast" tenant user wants to create new entries in the shop, because anything that the higher tenant (e.g. "West Coast") user creates has their DataKey and therefore can't be seen by a lower tenant user.

Not working with EntityFrameworkCore 6.0.0

When using the package in .Net 6.0 I run into this error: Method not found: 'System.Type Microsoft.EntityFrameworkCore.Metadata.ITypeBase.get_ClrType()'.

Here is the stack:

at AuthPermissions.DataLayer.EfCode.DataKeyQueryExtension.AddHierarchicalTenantReadOnlyQueryFilter(IMutableEntityType entityData, IDataKeyFilterReadOnly dataKey) at Infrastructure.Data.AppDbContext.OnModelCreating(ModelBuilder modelBuilder) in D:\GitHub\Infrastructure\Data\AppDbContext.cs:line 58 at Microsoft.EntityFrameworkCore.Infrastructure.ModelCustomizer.Customize(ModelBuilder modelBuilder, DbContext context) at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies) at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, ModelCreationDependencies modelCreationDependencies, Boolean designTime) at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel(Boolean designTime) at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model() at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__8_4(IServiceProvider p) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope) at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType) at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType) at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider) at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies() at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices() at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider() at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance() at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure1 accessor)
at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure1 accessor) at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.get_Dependencies() at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.EnsureCreated() at Web.Program.Main(String[] args) in D:\GitHub\Web\Program.cs:line 25

Here is the line in my AppDbContext that crashes:
entityType.AddHierarchicalTenantReadOnlyQueryFilter(this);

AddSingleTenantReadWriteQueryFilter for Entity type hierarchy

For an entity type hierarchy (using the default setup of TPH) checking for assignability to IDataKeyFilterReadWrite and using AddSingleTenantReadWriteQueryFilter to set up the query filter does not work.

All entities in the hierarchy share a single table in the DB and AddSingleTenantReadWriteQueryFilter will try to setup an (unnamed) index on the table for each entity in the hierarchy.

I think that the query filter needs setting on each entity in the hierarchy.
However, the DataKey property tweaks and the index only need adding to the table once at top level entity that has the property.

For TPT and TPC models it is quite probably different.

Happy to have a look at implementing.
Wanted to get any thoughts you had / check it wasn't already being worked on first.

Steven

Startup failing with EF Core 7

I have EF Core 7 referenced in my project and it seems that AuthP is breaking with this new version.

fail: AuthPermissions.AspNetCore.StartupServices.StartupServiceMigrateAuthPDatabase[0]
An error occurred while creating/migrating the SQL database.
System.MissingMethodException: Method not found: 'Microsoft.EntityFrameworkCore.Migrations.Operations.Builders.OperationBuilder1<Microsoft.EntityFrameworkCore.Migrations.Operations.CreateIndexOperation> Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder.CreateIndex(System.String, System.String, System.String, System.String, Boolean, System.String)'. at AuthPermissions.DataLayer.Migrations.Initial.Up(MigrationBuilder migrationBuilder) at Microsoft.EntityFrameworkCore.Migrations.Migration.BuildOperations(Action1 buildAction)
at Microsoft.EntityFrameworkCore.Migrations.Migration.get_UpOperations()
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.GenerateUpSql(Migration migration, MigrationsSqlGenerationOptions options)
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.MigrateAsync(String targetMigration, CancellationToken cancellationToken)
at AuthPermissions.AspNetCore.StartupServices.StartupServiceMigrateAuthPDatabase.ApplyYourChangeAsync(IServiceProvider scopedServices)

It works by downgrading to EF Core 6.

User who has rights in several tenants

Hello,

What a great, precise and clear job you did to build this multi-tenant permissions system.

I m trying to read your code an I have a question about the AuthUser and his tenant attribution. I can be competly wrong but it seems you always need to attach a "standard/normal" user to one tenant max (or tenant hierarchy).

And my question: can we imagine a case, when an external contractor will be affiliated to several companies owning a "tenant" each and in this case he will be forced to register with different userid/useremail for each tenant ? And if we choose to use external auth provider (Google/Azure other) he will not be able to register with the same auth method on serveral tenants ?

I m maybe missing something (a param or a segregation I didn't see or something) that allows this use case "horizontal multi-tenants user" ?

Thx again for this awesome github repo.

Version 2: Multi-tenant roles improvements

Version 1 of the AuthP library allowed you to create multi-tenant / SaaS applications and introduced the feature of a Tenant Admin who could manage users within a tenant. That reduces the work on the support people for your application.

In version 1 of AuthP library the Tenant Admin was limited to managing users in the Tenant Admin, BUT the Tenant Admin had access to every AuthP's Role (referred to a Role in this issue). This means the Tenant Admin could add roles to tenant users which gave total control over all its features, which is not a good idea.

In version 2 of the AuthP I will restrict the what a Tenant Admin can do, plus add a new feature so that you create different versions of your SaaS application (e.g. Free, Pro, Enterprise).

A Tenant admin cannot create / update / delete an Role

In version 1 of AuthP library I allowed a Tenant Admin to create / update / delete an Role, but they couldn't include advanced Permissions, which filters out advanced Permissions from a list of Permissions a Tenant Admin user. This stopped a Tenant Admin from creating a Role with an advanced Permissions.

But a problem existed in version 1 is if a Tenant Admin can create new Roles, then in Company1 could create a role that a Tenant Admin in Company2 would see. This is likely to cause confusion, especially if you have lots of tenants.

Therefore in AuthP version 2 a Tenant Admin should be barred from creating, updating or deleting a Role - only a App Admin (i.e. a user manages the whole application, not linked to tenant) can do that. The Tenant Admin's job is to manage tenant users and their Roles.

Restrict the more powerful Role from the Tenant Admin

In version 2 a Role will now have a new RoleType enum property with the following settings:

  • Normal, which means the Role can be used by any user. The Tenant Admin can only see Normal role.
  • HiddenFromTenant, which means the Role is not available to a user linked to a Tenant.

In AuthP version 2 a Role that contains a advanced Permission will, by default, have the RoleType property to HiddenFromTenant. A Role can also be set to HiddenFromTenant manually by the App Admin user.

Provide different versions of your application (e.g. Free, Pro, Enterprise).

Many SaaS applications have different versions, which allows you to get a range of income from different levels of users. In AuthP version 2 I introduce the concept of a Tenant Role.

Each Tenant will have a one-to-many link to the TenantRoles allocated to the Tenant. TenantRoles are created and added to a Tenant by the App Admin user, or it could be automated. The Tenant's TenantRoles be used in two ways:

  1. For applications that provide the same features to all the users, e.g. Visual Studio with its Community, Pro, Enterprise versions, the Tenant's list of TenantRoles are automatically added to the Roles that a user has. In this case the Role's RoleType property is set to TenantAutoAdd.
  2. For applications that has different types of users, e.g. Staff, Manager, CEO, then the Tenant's TenantRoles are manually added by a tenant admin user, e.g. an advanced paid-for TenantRole is only added to Managers. In this case the Role's RoleType property is set to TenantAdminAdd.

AuthP AzureAD handler: make it "create new AuthP user" include default roles

The AuthP AzureAD handler can add a new AuthP user if the user isn't already in the AuthP user list if the AddNewUserIfNotPresent setting is set to true. But the current version doesn't add any AuthP Roles to the new AuthP user.

In some cases you might want to provide some default AuthP Role(s) to the new user. We could achieve this adding a new AzureAD setting which holds a list of AuthP Role names that would be added to the new AuthP user.

Create Blazor Demo

It appears there's a lot of interest in an AuthP demo with a Blazor application.

I noticed one of the Discussions (#31), wanted to find out how to do this from a Blazor WASM application in particular.

So with @JonPSmith's blessing, I'll be taking the Example 4 project and converting it into a Blazor application.

I'll be creating

  • A Web API backend project
  • A Blazor WASM hosted project

Emails should be normalised, as the Postgres database is case sensative

In the current version of AuthP (3.2.0) the email of a user is stored as the user provides it. But an email is NOT case sensitive. This will be a problem as Postgres is case sensative.

The simplest way to do this is add a .Lower to the email in the AuthUser class

Things to do

  • Add a .Lower to the email in the AuthUser class (note: do after the username has as been set). DONE
  • Apply .Lower to the email in the AuthUsersAdmin FindAuthUserByEmailAsync method. DONE
  • Change SyncUsers to normalise Emails. DONE
  • Fix all tests containing emails. DONE
  • Change each Example to say that "NOTE: The email username is also the password".

Using with identityuser type int

hi there
i am trying to use IndividualAccountsAuthentication
which in my case class named AppUser inheriting from identity user
class AppUser : IdentityUser<int>
and i get an error
/*
Error CS0311 The type 'RivaWeb.Models.AppUser' cannot be used as type parameter 'TCustomIdentityUser' in the generic type or method 'SetupExtensions.IndividualAccountsAuthentication(AuthSetupData)'. There is no implicit reference conversion from 'RivaWeb.Models.AppUser' to 'Microsoft.AspNetCore.Identity.IdentityUser'
*/
is there something i am missing or there is no way i can use an int for the user id with this library.

MySQL support

I have an existing application that already uses MySQL. Can I use it with AuthPermissions library?

Potential error in Wiki for Setup Authentication

Hi Jon! Thank you for this amazing project and the hard work you are putting into it!
I was working on replacing IdentityUser with ApplicationUser in a app using AuthP, checked the wiki for what needed to be changed in my code, over here: https://github.com/JonPSmith/AuthPermissions.AspNetCore/wiki/Setup-Authentication

On point 3 it says the following:

There are some other code used in the examples that only work with UserManager (see below). If you want to use these you need to copy these too:

HostedServiceAddAspNetUsers
AspNetUserExtension, specifically ListAllAspNetUsers and CheckAddNewUserAsync

However it seems like ListAllAspNetUsers and HostedServiceAddAspNetUsers are no longer in the project (maybe renamed?). Just thought I'd point it out since this is a feature I'm guessing a lot of people will be/are using to extend the IdentityUser.

Have a great rest of your week! :)

Needs example of sociable unit testing/integration testing using WebApplicationFactory<Startup>

I've been unable to figure out how to mock authenticate in my existing sociable unit tests that use CustomWebApplicationFactory<Startup> to create a TestServer. They were working fine with role based authorization.

I tried following example 2 in Unit Test your AuthP app, and while that was working for getting services from factory.Services, it is not working when using factory.CreateClient().GetAsync("url").

I thought I could use a token created like in TestGenerateJwtTokenAsyncOk as a bearer token, but once I finally got the token generated, I got status code Unauthorized, WWW-Authenticate: Bearer error="invalid_token", error_description="The signature key was not found"

My Startup is applying the following configuration for this service:

            services.RegisterAuthPermissions<Permissions>()
                .AzureAdAuthentication(AzureAdSettings.AzureAdDefaultSettings(true))
                //... database method left out

After that my CustomWebApplicationFactory is applying the following configuration for this service:

                services.RegisterAuthPermissions<Permissions>(opt => opt.ConfigureAuthPJwtToken = CreateTestJwtSetupData())
                        .UsingInMemoryDatabase()
                        .SetupForUnitTestingAsync();

Enable Audit functionality on Tenant

I want to be able to add Audit functionality on Tenants and related dependent entities that are created in ITenantChangeService (i.e. CreatedBy, CreatedOn, UpdatedBy, UpdatedOn, etc). In my implementation, for example, I have a Company entity associated with the tenant. When the Tenant is added or modified, I want to record audit information about when it was modified and more importantly, by whom. Currently, this is not possible without some workaround code after the tenant is created and the TenantChangeService has been executed.

One solution might be to alter ITenantChangeService methods to accept an extra parameter of AuthUserId so the editing User can be recorded on the Tenant and any dependent entities updated. For example....

ITenantChangeService.CreateNewTenantAsync(Tenant tenant**, string? UserId = null**);

AuthPermissions.SupportCode.AddUsersServices.ISignInAndCreateTenant methods could then supply the relevant UserIID during Tenant change.

SQLite and GRPC

Hello!

I've reading through the wiki and examples for a little while now and find that this library is a nearly perfect fit for the project I am working on. With 2 missing bits.

We are using a SQLite DB for our application as it is working in an embedded environment where it is not practical to use SQLServer. I note there isn't anything built-in for supporting full SQLite DBs, but is it possible/practical to extend that from my code? Or would it need to be an addition to the library?

We are also using GRPC for our API, I'm not sure how well this library and GRPC will integrate at the moment, has anyone had a go at this?

.NET Core Identity

Would love to see this implemented but over .NET Core Identity with customization to it's classes. Any chance of that happening?

API using AuthP and AzureAd Token

Hello.

I hope I can properly explain the issue I am having.

I am currently working in a project (.Net7) and I am using Azure Ad first time and your library also for the first time.
Since one of the requirements is the usage of MariaDB.
That was the easy part thanks to the flexibility of your library.
As least the creation of the db and the tables.
AddRolesPermissionsIfEmpty
AddTenantsIfEmpty
AddAuthUsersIfEmpty
all work.

The issue is that I keep getting 403 in postman and the console
Authorization failed. These requirements were not met:
AuthPermissions.AspNetCore.PolicyCode.PermissionRequirement
[14:23:33 INF] AuthenticationScheme: Bearer was forbidden.
when I try [HasPermission] but [Authorize] works ok.

I tried following your Example 5 but in my case I use
AddMicrosoftIdentityWebApiAuthentication
if I use AddMicrosoftIdentityWebAppAuthentication postman returns an html page

I tried using
webBuilder.Services.RegisterAuthPermissions(opt => {
opt.TenantType = TenantTypes.SingleLevel;
})
.AzureAdAuthentication(AzureAdEventSettings.AzureAdDefaultSettings(JwtBearerDefaults.AuthenticationScheme))
.UsingEfCoreMariaDb("connection")
.AddRolesPermissionsIfEmpty(ApiAuthSetupData.RolesDefinition)
.AddTenantsIfEmpty(ApiAuthSetupData.TenantDefinition)
.AddAuthUsersIfEmpty(ApiAuthSetupData.UsersRolesDefinition)
.RegisterAuthenticationProviderReader()
.SetupAspNetCoreAndDatabase();

and

webBuilder.Services
.RegisterAuthPermissions(opt => {
opt.TenantType = TenantTypes.SingleLevel;
opt.ConfigureAuthPJwtToken = new AuthPJwtConfiguration
{
Issuer = jwtData.Issuer,
Audience = jwtData.Audience,
SigningKey = jwtData.SigningKey,
TokenExpires = new TimeSpan(0, 5, 0), //Quick Token expiration because we use a refresh token
RefreshTokenExpires = new TimeSpan(1, 0, 0, 0) //Refresh token is valid for one day
};
})
.AzureAdAuthentication(AzureAdEventSettings.AzureAdDefaultSettings(JwtBearerDefaults.AuthenticationScheme))
.UsingEfCoreMariaDb("connection")
.AddRolesPermissionsIfEmpty(ApiAuthSetupData.RolesDefinition)
.AddTenantsIfEmpty(ApiAuthSetupData.TenantDefinition)
.AddAuthUsersIfEmpty(ApiAuthSetupData.UsersRolesDefinition)
.RegisterAuthenticationProviderReader()
.SetupAspNetCoreAndDatabase();

Not sure what I am missing, I hope you can point in the right direction.

Thank you.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.