I am about to start a project that needs custom identity authetication and authorization.
For authentication I am asking this question.
I need to know how can I create ExampleDBContext class that extends from DBContext class? and what settings I need to do in Startup.Auth.cs to make it work. And for registration, what should be the name of the registration table and what should be its columns? and what other tables and their columns need to be there?
There is so much info related to identity authentication but nothing actually useful. thanks
I'm not sure but I think you want something like this:
ExampleDbContext.cs
public class ExampleDbContext : IdentityDbContext<User>, IExampleDbContext
{
public ExampleDbContext()
: base("yourConnectionStringName", throwIfV1Schema: false)
{
}
public static ExampleDbContext Create()
{
return new ExampleDbContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<IdentityRole>().ToTable("Roles");
modelBuilder.Entity<IdentityUserRole>().ToTable("UserRoles");
modelBuilder.Entity<IdentityUserLogin>().ToTable("UserLogins");
modelBuilder.Entity<IdentityUserClaim>().ToTable("UserClaims");
}
void IDisposable.Dispose()
{
this.Dispose();
}
}
You can use the code first method to generate the tables. That means that you can customize your User model the way you want. Here is the example of
User.cs
public class User : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public string FirstName { get; set; }
public string LastName { get; set; }
}
In the Users you can see that I've added some custom fields (FirstName LastName) and if you use the code first you can generate the custom Users table and use it for authentication. No need to add anything in Startup.Auth.cs you just need to setup dependency injection correctly.
Related
Am learning about data sync from API to WPF app. Got a demo from https://github.com/Azure/azure-mobile-apps/tree/main/samples. But I got into a problem that all the data inside the tables are collected on the call but I need to select specific data using Id. Tried a query etc all came to nothing. Please guide me
Thank you
PatientsController.cs
[Route("tables/Patients")]
public class PatientsController : TableController<Patients>
{
public PatientsController(AppDbContext context)
: base(new EntityTableRepository<Patients>(context))
{
}
}
AppDbContext.cs
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<Patients> Patients => Set<Patients>();
}
Try to use the following code:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<Patients> Patients {get;set;}
}
controller:
[Route("tables/Patients")]
public class PatientsController : TableController<Patients>
{
private readonly AppDbContext _context;
public PatientsController(AppDbContext context)
: base(new EntityTableRepository<Patients>(context))
{
_context=context;
}
public async Task<IActionResult> Index(int id){
var Patients=_context.Patients.FindAsync(id);
return View(Patients);
}
}
If you just need to get a record by Id, you use the URL https://mysite/tables/myTable/id - no search required - it will go directly to the entity you want.
If you need to limit what a user can see, you will need to implement an access control provider (which is an implementation of IAccessControlProvider). This provides three methods to limit what a user can see and do model updates for ensuring the write operations store the right thing. You can read more here: https://learn.microsoft.com/en-us/azure/developer/mobile-apps/azure-mobile-apps/howto/server/dotnet-core#configure-access-permissions
If it's something else, you'll need to be a little more specific. I hang out in the issues and discussions of the azure/azure-mobile-apps repo, so feel free to file a question there.
I create a React App using Visual Studio 2022 ASP.NET React Project Template. I was able to to modify the project to add some custom fields during registration. Those customer fields include First & Last Name.
I'd like to replace the email that is displayed on every page within the navigation menu:
I managed to successfully replace it on the Razor pages that are scaffolded in the Identity Framework.
But I can't seem to figure out how to do it on the React side of things. Any documentation you can point me is greatly appreciated.
Go to ClientApp/src/components/api-authorization/LoginMenu.js and change populateState method to:
async populateState() {
const [isAuthenticated, user] = await Promise.all([authService.isAuthenticated(), authService.getUser()])
this.setState({
isAuthenticated,
userName: user && `${user.given_name} ${user.family_name}`
});
}
To map the FirstName and LastName properties to given_name and family_name claims you can use UserClaimsPrincipalFactory:
public class AppUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
public AppUserClaimsPrincipalFactory(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
IOptions<IdentityOptions> optionsAccessor)
: base(userManager, roleManager, optionsAccessor)
{
}
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
{
var identity = await base.GenerateClaimsAsync(user);
identity.AddClaim(new Claim(JwtClaimTypes.GivenName, user.FirstName));
identity.AddClaim(new Claim(JwtClaimTypes.FamilyName, user.LastName));
identity.AddClaim(new Claim(JwtClaimTypes.BirthDate, user.DateOfBirth.ToString("yyyy-MM-dd"), ClaimValueTypes.Date));
return identity;
}
}
And in Startup.cs add:
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, AppUserClaimsPrincipalFactory>();
Assuming you have defined ApplicationUser as:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
I created a class for getting my settings of website. In this settings table I'm storing phone number, email adress, address etc. Admin user can change their settings.
public interface IAppGlobalVariableService
{
Task<List<Configuration>> GetAllConfig();
Task<string> GetValue(string name);
}
public class AppGlobalVariablesService : IAppGlobalVariableService
{
private readonly IRepository<Configuration> _configRepository;
private static Task<List<Configuration>> _configList;
public AppGlobalVariablesService(IRepository<Configuration> configRepository)
{
_configRepository = configRepository;
_configList = GetAllConfig();
}
public async Task<List<Configuration>> GetAllConfig()
{
return await _configRepository.GetAll().ToListAsync();
}
public Task<string> GetValue(string name)
{
return GetConfigByName(name);
}
private static async Task<string> GetConfigByName(string name)
{
var configList = await _configList;
return configList.FirstOrDefault(x => x.ConfigName == name)?.ConfigValue;
}
}
In layout, I'm calling my settings like view components style.
#await AppConfig.GetValue("Facebook")
I injected _ViewImports
#inject IAppGlobalVariableService AppConfig
I will use this code most of place. Do you have any idea to reduce queries for dotnet core 2.2 ? Can I do something like querying database once to get variables. And store cache ?
UPDATE:
I updated my startup.cs file (AddTransient to AddScoped) It redureced queries. But I want to learn can I use single query ?
services.AddScoped<IAppGlobalVariableService, AppGlobalVariablesService>();
I use boilerplate template with Angular, .NET Framework and Module Zero. I have added Tenant to User class and User list to Tenant class
public class Tenant : AbpTenant<User>
{
public Tenant()
{
this.Users = new HashSet<User>();
}
public ICollection<User> Users { get; set; }
public Tenant(string tenancyName, string name)
: base(tenancyName, name)
{
}
}
public class User : AbpUser<User>
{
public const string DefaultPassword = "123qwe";
public virtual Tenant Tenant { get; set; }
public static string CreateRandomPassword()
{
return Guid.NewGuid().ToString("N").Truncate(16);
}
public static User CreateTenantAdminUser(int tenantId, string emailAddress, string password)
{
var user = new User
{
TenantId = tenantId,
UserName = AdminUserName,
Name = AdminUserName,
Surname = AdminUserName,
EmailAddress = emailAddress,
Password = new PasswordHasher().HashPassword(password)
};
return user;
}
}
Now when I add migration I get
public override void Up()
{
AddColumn("dbo.AbpUsers", "Tenant_Id", c => c.Int());
CreateIndex("dbo.AbpUsers", "TenantId");
CreateIndex("dbo.AbpUsers", "Tenant_Id");
AddForeignKey("dbo.AbpUsers", "Tenant_Id", "dbo.AbpTenants", "Id");
AddForeignKey("dbo.AbpUsers", "TenantId", "dbo.AbpTenants", "Id");
}
Why entity framework creates another foreign key and how should I add users list to Tenant?
I'm surprised you could add a migration. I got this while trying to reproduce:
Unable to determine the relationship represented by navigation property 'User.Tenant' of type 'Tenant'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I got it to work by adding an InverseProperty attribute:
public class Tenant : AbpTenant<User>
{
[InverseProperty("Tenant")]
public ICollection<User> Users { get; set; }
}
I am sending some simple user details to a Nancy module. I am taking advantage of Nancy's model binding feature to harvest the user details from the request and pass them to my UserService.Add(...) method, like this:
Nancy Module
Post["/add"] = parameters =>
{
var user = this.Bind<UserDetails>();
UserService.Add(user);
return HttpStatusCode.OK;
};
User Details Class
public class UserDetails
{
public string UserName { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
User Service
public static void Add(UserDetails user)
{
//Add the user
}
This works and makes for terse syntax in the Nancy Module. However it means that I am forced to create a data-transfer (DTO) class simply to harvest the Request payload.
Is it possible to avoid this intermediary class altogether? Instead of binding a class to the payload, would it be possible to bind the Method parameters instead?
This would give code that might look something like this:
Nancy Module
Post["/add"] = parameters =>
{
this.BindAndCall<UserService>("Add");
return HttpStatusCode.OK;
};
User Service
public static void Add(string firstName, string lastName, string email, string userName, string password)
{
//Add the user
}
You can always create some extension methods or custom binder, but then you are coupling the internal implementation of your service to the exact naming of the parameters for the payload, which is not good, as later when your service changes, you need to change the payload or jump trough hoops. There's nothing wrong with the DTO, it costs nothing to create.
The ParameterBag - A Partial Solution to DTO Proliferation
To my mind, DTOs are pain as they tend to proliferate into a shadow-domain. This creates headaches when you decide to reorganise your service layer.
So why not just use dynamic types and forget all about DTOs and strongly-typed route parameters? Well dynamic values are very convenient but they have their own problems, chiefly that you cannot pass them to extension methods. So it is just easier to have the route parameters correctly presented, hence model binding, hence DTOs and hence spaghetti and silly hats.
So here is a partial solution relies on Nancy's Model Binding. It significantly reduces route-level ceremony and helps contain that annoying DTO proliferation.
Nancy Base Module
public class _BaseModule : NancyModule
{
public class ParameterBag
{
// All the params used across all routes, GET and POST
public string UserName { get { return this.Value; } }
public string UserIds { get; set; }
public string UserId { get; set; }
public string Value { get; set; }
public int Skip { get; set; }
public int Take { get; set; }
}
public ParameterBag Params;
public _BaseModule(): this(""){}
public _BaseModule(string modulePath): base(modulePath)
{
Before += ctx =>
{
this.Params = this.Bind<ParameterBag>();
return null;
};
}
}
The ParameterBag class contains all the parameters that I am interested in binding across all routes. Since Nancy will only populate the properties if it finds a matching field in the payload, you can just add as many properties to this class as you like without caring if they will be used by a given route or not.
Note how the Binding takes place in the Before hook. This means that every route (that derives from the _BaseModule) will automatically bind any matching parameter values to the universal ParameterBag class properties. No specific route-level intervention required.
The effect of this is to provide the route handlers with a strongly typed parameter values that can be just used.
Nancy Module
public class UserModule : _BaseModule
{
public UserModule()
{
// handlers go here
}
}
Route Handler Example
Get["/user/{userid}/username/available"] = _ =>
{
return Response.AsJson(new
{
// the username is a hidden value
// the userid comes from the url
value = Params.UserName,
valid = UserService.UserNameAvailable(Params.UserName, Params.UserId)
}
);
};
Example Usage
The example below relies on the jqBootstrapValidation. It shows how the Binding trick works for parameter data supplied on the URL and provided as part of an ajax payload (see value attribute).
<input
type="text"
id="username"
name="username"
placeholder="User Name"
data-validation-ajax-ajax="/user/#user.id/username/available"
data-validation-ajax-message="This name has already been taken"
value="#user.UserName"
required>