ASP.NET Identity with Entity Framework seeding the database issue - asp.net

I'm using Identity with framework entity.
I'm trying to seed my database with some data - 1 user, and 1 role.
The role is getting seeded properly and everything is good.
The user is not getting seeded - the issue.
Program.cs file
public class Program
{
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using var scope = host.Services.CreateScope();
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<DataContext>();
var userManager = services.GetRequiredService<UserManager<AppUser>>();
var roleManager = services.GetRequiredService<RoleManager<AppRole>>();
await context.Database.MigrateAsync();
await Roles.RoleInitalizer(roleManager);
await Seed.SeedUsers(userManager);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred during migration");
}
await host.RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Roles.cs file - works as intended
public class Roles
{
public static async Task RoleInitalizer(RoleManager<AppRole> roleManager)
{
var roles = new List<AppRole>
{
new AppRole{Name="User"}
};
foreach (var role in roles)
{
await roleManager.CreateAsync(role);
}
}
}
The seed.cs file - user seeding - the issue is here
public class Seed
{
public static async Task SeedUsers(UserManager<AppUser> userManager)
{
if (await userManager.Users.AnyAsync()) return;
var userData = await System.IO.File.ReadAllTextAsync("Data/Seed/UserSeedData.json");
var users = JsonSerializer.Deserialize<List<AppUser>>(userData);
if (users == null) return;
foreach (var user in users)
{
user.UserName = user.UserName.ToLower();
await userManager.CreateAsync(user, "#Test123456789");
await userManager.AddToRoleAsync(user, "User");
}
}
}
AppUser.cs File
public class AppUser : IdentityUser
{
public ICollection<Hero> Heroes { get; set; }
public ICollection<AppUserRole> UserRoles { get; set; }
}
ConfigureService from startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAplicationServices(_config);
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
});
services.AddCors();
services.AddIdentityServices(_config);
}
AddAplicationServices Function from startup
public static IServiceCollection AddAplicationServices(this IServiceCollection services, IConfiguration config)
{
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IHeroRepository, HeroRepository>();
services.AddScoped<ITokenService, TokenService>();
services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);
services.AddControllersWithViews()
.AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
services.AddDbContext<DataContext>(options =>
{
options.UseSqlServer(config.GetConnectionString("DefaultConnection"));
});
return services;
}
AddIdentityServices function from startup.cs
public static IServiceCollection AddIdentityServices(this IServiceCollection services, IConfiguration config)
{
services.AddIdentityCore<AppUser>(opt =>
{
opt.Password.RequireNonAlphanumeric = false;
})
.AddRoles<AppRole>()
.AddRoleManager<RoleManager<AppRole>>()
.AddSignInManager<SignInManager<AppUser>>()
.AddRoleValidator<RoleValidator<AppRole>>()
.AddEntityFrameworkStores<DataContext>();
// services.TryAddSingleton<ISystemClock, SystemClock>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = "yad2",
ValidIssuer = "yad",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"])),
};
});
services.AddAuthorization(opt =>
{
opt.AddPolicy("RequireUserRole", policy => policy.RequireRole("User"));
});
return services;
}
The UserSeedData.json file
[
{
"UserName": "Test",
"Email": "test#test.com"
}
]
Here is the project stracture
This is the project from GitHub just in case
https://github.com/davidax0204/proj
I'm not able to figure out why the roles getting seeded properly and the user is not.
I double-checked that the seeding file has the correct structure as the table of the app user.
I'm getting dropping the old database and starting the project again and only the role is getting seeded every time.
I have tried to manipulate the Seed file but nothing seems to work for me.
Can someone please explain to me why that issue happens so I can learn from my mistakes?
And how can I solve that issue?

Related

Xamarin Connection Refused when connecting to Netcore 3.1 localhost rest api using Kestrel from mobile app

I am using MVVM Prism for the Xamarin mobile app.
I created a NetCore 3.1 rest api that serves as the backend to my Xamarin mobile app. I want to be able to debug it locally.
I would like to:
A. Connect to localhost to debug the rest api from the Simulators
B. Also would like to connect to the localhost kestrel from the mobile app itself in debug mode if possible
I am using NetCore Kestrel instead of IIS to host the rest api in the localhost.
I can connect with Postman locally and DEBUG ok in VS using this URL:
https://localhost:44349/api/Accounts/Register
even though I do get a first certificate validation error, but I turned off Certificate validation in POSTMAN to make it work, but when I try to connect to that same URL from the mobile, with the code below I get this error below.
The error is triggered in LoginPageViewModel.cs in the SignInWithFacebookTapped function after calling ApiServices register function:
I have been struggling with this for 2 full days, this is my code:
REST API PROJECT
Program.cs class:
namespace MyAppNamespace
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
services.Configure<KestrelServerOptions>(
context.Configuration.GetSection("Kestrel"));
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(serverOptions =>
{
// Set properties and call methods on options
serverOptions.Limits.MaxConcurrentConnections = 100;
serverOptions.Limits.MaxConcurrentUpgradedConnections = 100;
serverOptions.Limits.MaxRequestBodySize = 10 * 1024;
serverOptions.Limits.MinRequestBodyDataRate =
new MinDataRate(bytesPerSecond: 100,
gracePeriod: TimeSpan.FromSeconds(10));
serverOptions.Limits.MinResponseDataRate =
new MinDataRate(bytesPerSecond: 100,
gracePeriod: TimeSpan.FromSeconds(10));
serverOptions.Listen(IPAddress.Loopback, 5000);
serverOptions.Listen(IPAddress.Loopback, 5001,
listenOptions =>
{
listenOptions.UseHttps(",myapphosting.pfx",
"mypassword"); // I did not specify where these files are in the code, it seems to just know?
});
serverOptions.Limits.KeepAliveTimeout =
TimeSpan.FromMinutes(2);
serverOptions.Limits.RequestHeadersTimeout =
TimeSpan.FromMinutes(1);
})
.UseStartup<Startup>();
});
}
}
Startup class:
namespace MyAppNamespace
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddMvc(option => option.EnableEndpointRouting = false)
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddNewtonsoftJson(opt => opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"])),
ClockSkew = TimeSpan.Zero,
};
});
services.AddDbContext<MyAppDbContext>(option => option.UseSqlServer("MyConnectionString"));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, MyDbContext myDbContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//else
//{
// app.UseHsts();
//}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
myDbContext.Database.EnsureCreated();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
launchSettings.Json :
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iis": {
"applicationUrl": "http://localhost/MyWebApiName",
"sslPort": 0
},
"iisExpress": {
"applicationUrl": "http://localhost:52080",
"sslPort": 44349
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/accounts/register",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"YWAW.WebApi": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "api/accounts/register",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}
I followed this tutorial to set up the to bypass the Bypass the certificate security check and grab an insecurehandler on both Android and iOS platforms.
https://learn.microsoft.com/en-us/xamarin/cross-platform/deploy-test/connect-to-local-web-services
XAMARIN FORMS PROJECT
in Constants class(shared project):
public static string BaseAddress =
Device.RuntimePlatform == Device.Android ? "https://10.0.2.2:5001" : "https://localhost:5001"; // here I tried writing the iOS url as in Postman above too, same result
public static string RestUrl = $"{BaseAddress}/api/Accounts/Register";
App.Xaml.cs
public partial class App
{
/*
* The Xamarin Forms XAML Previewer in Visual Studio uses System.Activator.CreateInstance.
* This imposes a limitation in which the App class must have a default constructor.
* App(IPlatformInitializer initializer = null) cannot be handled by the Activator.
*/
public App() : this(null) { }
public App(IPlatformInitializer initializer) : base(initializer) { }
protected override async void OnInitialized()
{
InitializeComponent();
await NavigationService.NavigateAsync("NavigationPage/HomePage");
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<IAppInfo, AppInfoImplementation>();
containerRegistry.RegisterForNavigation<NavigationPage>();
containerRegistry.RegisterForNavigation<MainPage, MainPageViewModel>();
containerRegistry.RegisterForNavigation<HomePage, HomePageViewModel>();
containerRegistry.RegisterForNavigation<LoginPage, LoginPageViewModel>();
containerRegistry.RegisterForNavigation<SignUpPage, SignUpPageViewModel>();
containerRegistry.RegisterForNavigation<DefaultCustomActivityIndicatorPage, DefaultCustomActivityIndicatorPageViewModel>();
containerRegistry.RegisterForNavigation<DiscoverDealsTabbedPage, DiscoverDealsTabbedPageViewModel>();
containerRegistry.Register<ApiServices>(); //not sure if I actually should register this too
}
// add for app center analytics and crashes
protected override void OnStart()
{
base.OnStart();
AppCenter.Start("ios=secretkey" +
"uwp={Your UWP App secret here};" +
"android={Your Android App secret here}",
typeof(Analytics), typeof(Crashes));
}
}
LoginPageViewModel.cs
public class LoginPageViewModel : BindableBase
{
private IPageDialogService _dialogService { get; }
private ApiServices _apiService { get; }
public DelegateCommand SignInWithEmailTappedCmd { get; set; }
public DelegateCommand SignInWithFacebookTappedCmd { get; set; }
public DelegateCommand SignInWithGoogleTappedCmd { get; set; }
private IFacebookClient _facebookService = CrossFacebookClient.Current;
private IGoogleClientManager _googleService = CrossGoogleClient.Current;
public INavigationService NavigationService { get; set; }
private readonly ICustomActivityIndicatorPage _customActivityIndicator;
private string _emailAddress;
public string EmailAddress
{
get => _emailAddress;
set => SetProperty(ref _emailAddress, value);
}
private string _password;
public string Password
{
get => _password;
set => SetProperty(ref _password, value);
}
public LoginPageViewModel(ICustomActivityIndicatorPage customActivityIndicator,
IHttpClientHandlerService httpClientHandlerService,
INavigationService navigationService,
IPageDialogService dialogService)
{
_customActivityIndicator = customActivityIndicator;
SignInWithEmailTappedCmd = new DelegateCommand(SignInWithEmailTapped);
SignInWithFacebookTappedCmd = new DelegateCommand(async() => await SignInWithFacebookTapped());
SignInWithGoogleTappedCmd = new DelegateCommand(async() => await SingInWithGoogleTapped());
NavigationService = navigationService;
_dialogService = dialogService;
_apiService = new ApiServices(httpClientHandlerService);
}
/// <summary>
///
/// </summary>
/// <returns></returns>
private async Task SignInWithFacebookTapped()
{
try
{
if (_facebookService.IsLoggedIn)
_facebookService.Logout();
EventHandler<FBEventArgs<string>> userDataDelegate = null;
_customActivityIndicator.InitActivityPage(new DefaultCustomActivityIndicatorPage());
_customActivityIndicator.ShowActivityPage();
userDataDelegate = async (object sender, FBEventArgs<string> e) =>
{
switch (e.Status)
{
case FacebookActionStatus.Completed:
var facebookProfile = await Task.Run(() => JsonConvert.DeserializeObject<FacebookProfile>(e.Data));
// save the user to the db if doesn't exist
UserToRegister user = new UserToRegister
{
Email = facebookProfile.Email,
FirstName = facebookProfile.FirstName,
LastName = facebookProfile.LastName
};
// THIS IS WHERE I TRY TO ACCESS THE LOCALHOST REST API
**var registerOutcome = await _apiService.Register(user);**
await NavigationService.NavigateAsync("DiscoverDealsTabbedPage");
_customActivityIndicator.HideActivityPage();
break;
case FacebookActionStatus.Canceled:
_customActivityIndicator.HideActivityPage();
await _dialogService.DisplayAlertAsync("Facebook Auth", "Canceled", "Ok");
break;
case FacebookActionStatus.Error:
_customActivityIndicator.HideActivityPage();
await _dialogService.DisplayAlertAsync("Facebook Auth", "Error", "Ok");
break;
case FacebookActionStatus.Unauthorized:
_customActivityIndicator.HideActivityPage();
await _dialogService.DisplayAlertAsync("Facebook Auth", "Unauthorized", "Ok");
break;
}
_facebookService.OnUserData -= userDataDelegate;
};
_facebookService.OnUserData += userDataDelegate;
string[] fbRequestFields = { "email", "first_name", "picture", "gender", "last_name" };
string[] fbPermisions = { "email" };
await _facebookService.RequestUserDataAsync(fbRequestFields, fbPermisions);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
_customActivityIndicator.HideActivityPage();
}
}
}
ApiServices class:
public class ApiServices
{
private HttpClient httpClient;
private string apiRegisterURL;
private readonly IHttpClientHandlerService _httpClientHandler;
public ApiServices(IHttpClientHandlerService httpClientHandler)
{
#if DEBUG
_httpClientHandler = httpClientHandler;
httpClient = new HttpClient(_httpClientHandler.GetInsecureHandler());
apiRegisterURL = Constants.RestUrl;
#else
httpClient = new HttpClient();
apiRegisterURL = Constants.REGISTER_URL;
#endif
}
/// <summary>
///
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public async Task<RegisterOutcome> Register(UserToRegister user)
{
RegisterOutcome outcome = new RegisterOutcome();
//var httpClient = new HttpClient();
try
{
var json = JsonConvert.SerializeObject(user);
var content = new StringContent(json, Encoding.UTF8, "application/json");
outcome.ResponseMessage = await httpClient.PostAsync(apiRegisterURL, content);
}
catch (Exception ex) // ERROR IS HERE CONNECTION REFUSED
{
outcome.ErrorMessage = ex.Message;
}
return outcome;
}
}
IOS PROJECT:
iOSHttpClientHandlerService.cs:
[assembly: Dependency(typeof(iOSHttpClientHandlerService))]
namespace YWAWMobileApp.iOS.Services
{
public class iOSHttpClientHandlerService : IHttpClientHandlerService
{
public HttpClientHandler GetInsecureHandler()
{
HttpClientHandler handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
if (cert.Issuer.Equals("CN=localhost"))
return true;
return errors == System.Net.Security.SslPolicyErrors.None;
};
return handler;
}
}
}
AppDelegate.cs:
public class iOSInitializer : IPlatformInitializer
{
public void RegisterTypes(IContainerRegistry containerRegistry)
{
// Register any platform specific implementations
containerRegistry.Register<ICustomActivityIndicatorPage, iOSCustomActivityIndicatorPage>();
containerRegistry.Register<IHttpClientHandlerService, iOSHttpClientHandlerService>();
}
}
ANDROID project same as IOS.
In your web api project go to Program.cs file and in the CreateWebHostBuilder method add this.
.UseUrls("https://*:5001") // Add your port number or url scheme (http or https) on which your apis running instead of 5001.
Then change you base url like this.
public static string BaseAddress = "https://{here write your local ip address}:5001";

Net Core 3.1 API with Identity Server 4 custom password validation

I'm building an API using identity server and I need to use an existing database. The users password are stored with a custom hash password.
I use FindClientByIdAsync to validate user and password, but as the password is encrypted in a non-standard algorithm I get invalid_client error message. If I change in execution time (with breakpoint) the value of password for an unencrypted value the authentication works.
It's possible change the client_secret validation for FindClientByIdAsync?
Custom ClientStore class
public class ClientStore : IClientStore
{
private readonly IMyUserRepository myUserRepository;
public ClientStore(IMyUserRepository myUserRepository)
{
this.myUserRepository = myUserRepository;
}
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId()
};
}
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("My_API", "My API")
};
}
public async Task<Client> FindClientByIdAsync(string client)
{
var user = await myUserRepository.GetUserByEmailAsync(client);
if (user == null)
return null;
return new Client()
{
ClientId = client,
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
ClientSecrets =
{
new Secret(user.Password.Sha256()) //if I change to unencrypted works, but the value in database is hashed
},
AllowedScopes = { "GOLACO_API", IdentityServerConstants.StandardScopes.OpenId }
};
}
}
Identity server configuration in Startup class
services.AddIdentityServer(options =>
{
options.Events.RaiseSuccessEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseErrorEvents = true;
})
.AddSigningCredential(GetSigningCredential()) // here I just read the private.key file
.AddInMemoryIdentityResources(ClientStore.GetIdentityResources())
.AddInMemoryApiResources(ClientStore.GetApiResources())
.AddClientStore<ClientStore>();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["Configuration"];
options.ApiName = "My_API";
options.RequireHttpsMetadata = false;
});
services.AddAuthentication()
.AddFacebook("Facebook", options =>
{
options.AppId = "1234";
options.AppSecret = "1234567890";
});
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
You have to implement IResourceOwnerPasswordValidator as Damien showed in his blog
public class CustomResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
private readonly IUserRepository _userRepository;
public CustomResourceOwnerPasswordValidator(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
if (_userRepository.ValidateCredentials(context.UserName, context.Password))
{
var user = _userRepository.FindByUsername(context.UserName);
context.Result = new GrantValidationResult(user.SubjectId, OidcConstants.AuthenticationMethods.Password);
}
return Task.FromResult(0);
}
}
And add builder.AddResourceOwnerValidator<CustomResourceOwnerPasswordValidator>(); in the startup file.

AuthorizeView Roles doesn't recognize role even though the code does

I'm trying to set up authorization with Blazor .net core 3.1 and the AuthorizeView Roles doesn't seem to recognize the role that is in the database. On the other hand, if I try to do it in code and I've set up a little message if it finds the role with the user, it finds the role and displays the ""User is a Valid User" message. I'm using this with AzureAd Microsoft authentication and AspNetCore identity package.
Here's the index page code
#page "/"
#using Microsoft.AspNetCore.Authorization;
#using Microsoft.AspNetCore.Identity;
#inject UserManager<IdentityUser> _UserManager
#inject RoleManager<IdentityRole> _RoleManager
#inject AuthenticationStateProvider AuthenticationStateProvider
#attribute [Authorize]
<span>#Message</span>
<AuthorizeView Roles="Users">
<Authorized>
<p>Youre In!</p>
</Authorized>
</AuthorizeView>
#code
{
[CascadingParameter]
private Task<AuthenticationState> authStateTask { get; set; }
string USER_ROLE = "Users";
string CurrentEmail;
string Message;
protected override async Task OnInitializedAsync()
{
var authState = await authStateTask;
var CurrentEmail = authState.User.Identity.Name;
if (CurrentEmail.Contains("#users.com") == true)
{
var user = await _UserManager.FindByNameAsync(CurrentEmail);
if (user == null)
{
var newUser = new IdentityUser { UserName = CurrentEmail, Email = CurrentEmail };
var createResult = await _UserManager.CreateAsync(newUser);
if (createResult.Succeeded)
{
var roleResult = await _UserManager.AddToRoleAsync(newUser, USER_ROLE);
if (roleResult.Succeeded)
{
Message = ("Good job");
}
}
}
else
{
var RoleResult = await _UserManager.IsInRoleAsync(user, USER_ROLE);
if(RoleResult == true)
{
Message = "User is a Valid User";
}
else
{
Message = "User is invalid";
}
}
}
}
}
Here are my services:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options => options.UseSqlite("DataSource=db.db"));
services.AddDefaultIdentity<IdentityUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>();
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
}

Seeding Database in Asp.net Core, cannot create admin account

I'm trying to create a default admin account when I start an application.
Now what I'm interested in is how to seed a database in asp.net core. I have a seed code that I run in the main program. It shows no error but does not update the database. I've been trying to change "Identity rolls" to Application Role in my SeedData, but it has no effect at all.
I wouldn't want to change most of the code and I know it can be done with a model builder, but I don't want it that way. I think the problem is with the main program, but I don't understand what I need to change. My code is shown here.
SeedData.cs
namespace AspNetCoreTodo
{
public static class SeedData
{
public static async Task InitializeAsync(IServiceProvider services)
{
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
await EnsureRolesAsync(roleManager);
var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
await EnsureTestAdminAsync(userManager);
}
private static async Task EnsureRolesAsync(RoleManager<IdentityRole> roleManager)
{
var alreadyExists = await roleManager.RoleExistsAsync(Constants.AdministratorRole);
if (alreadyExists) return;
await roleManager.CreateAsync(new IdentityRole(Constants.AdministratorRole));
}
private static async Task EnsureTestAdminAsync(UserManager<ApplicationUser> userManager)
{
var testAdmin = await userManager.Users
.Where(x => x.UserName == "admin#todo.local")
.SingleOrDefaultAsync();
if (testAdmin != null) return;
testAdmin = new ApplicationUser { Email = "admin#todo.local", UserName = "admin#todo.local" };
await userManager.CreateAsync(testAdmin, "NotSecure123!!");
await userManager.AddToRoleAsync(testAdmin, Constants.AdministratorRole);
}
}
}
ApplicationDbContext.cs
namespace AspNetCoreTodo.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<TodoItem> Items {get; set;}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
}
Program.cs
namespace AspNetCoreTodo
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
private static void InitializeDatabase(IWebHost host)
{
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
SeedData.InitializeAsync(services).Wait();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
Startup.cs //Configuration
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
services.AddMvc();
services.AddAuthentication();
services.AddScoped<ITodoItemService, TodoItemService>();
}
Does your application ever goes in the method InitializeDatabase(IWebHost host) in Program.cs?
Could you please try to call your method in Main() method:
public static void Main(string[] args)
{
var webHost = CreateWebHostBuilder(args).Build();
InitializeDatabase(webHost);
webHost.Run();
}
Note: You have to create that 'webHost' variable, because your method takes 'IWebHost' as a parameter. And CreateWebHostBuilder(string[] args) method returns type of IWebHostBuilder. Also Run() method will work on type of IWebHost.
Note: As Nilay noticed above, I'd also seed my database in Startup.cs, in the
if(env.isDevelopment){
InitializeDatabase(webHost);
}
Because normally, seeding is a "development" purpose.

Token auth in asp mvc 6

It seems like there is not a lot of information about how to do authorization with the new MVC version. Since ASP 5 now is in RC 1 one could guess that you now can start trying to understand how its going to work...
What I want to do is just a simple example of an auth token that contains the user's name and roles.
A link like http://bitoftech.net/2015/03/11/asp-net-identity-2-1-roles-based-authorization-authentication-asp-net-web-api/ would help greatly but seems hard to find
You can try OpenIddict for that. You need RC2 to use it, but it's quite easy to set up:
public void ConfigureServices(IServiceCollection services) {
services.AddMvc();
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddOpenIddict(); // Add the OpenIddict services after registering the Identity services.
}
public void Configure(IApplicationBuilder app) {
app.UseOpenIddict();
}
Sean Walsh posted a detailed walkthrough on his blog: http://capesean.co.za/blog/asp-net-5-jwt-tokens/.
You can use OpenIdConnect.Server. You can set it up like this
Startup.cs
public class Startup {
public IConfigurationRoot configuration { get; set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv) {
IConfigurationBuilder builder = new ConfigurationBuilder();
configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services) {
services.AddIdentity<ApplicationUser, IdentityRole>(options => {
options.User.RequireUniqueEmail = true;
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireNonLetterOrDigit = false;
options.Password.RequiredLength = 6;
}).AddEntityFrameworkStores<DataModelContext>();
}
public void Configure(IApplicationBuilder app) {
app.UseJwtBearerAuthentication(new JwtBearerOptions {
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Audience = "OAuth:Audience",
Authority = "OAuth:Authority",
RequireHttpsMetadata = false
});
app.UseOpenIdConnectServer(options => {
options.Issuer = new Uri("OpenId:Issuer");
options.AllowInsecureHttp = true;
options.AuthorizationEndpointPath = PathString.Empty;
options.Provider = new AuthorizationProvider();
});
}
}
AuthorizationProvider.cs
public class AuthorizationProvider : OpenIdConnectServerProvider {
public override Task ValidateTokenRequest(ValidateTokenRequestContext context) {
context.Skip();
return Task.FromResult(0);
}
public override Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsContext context) {
string username = context.UserName;
string password = context.Password;
UserManager<ApplicationUser> userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>();
ApplicationUser user = userManager.FindByNameAsync(username).Result;
if (userManager.CheckPasswordAsync(user, password).Result) {
ClaimsIdentity identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.Name, username,
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
List<string> roles = userManager.GetRolesAsync(user).Result.ToList();
foreach (string role in roles) {
identity.AddClaim(ClaimTypes.Role, role,
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
}
AuthenticationTicket ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
ticket.SetResources("OAuth:Audience");
List<string> scopes = new List<string>();
if (context.Request.HasScope("offline_access")) {
scopes.Add("offline_access");
}
ticket.SetScopes(scopes);
context.Validate(ticket);
} else {
context.Reject("invalid credentials");
}
return Task.FromResult(0);
}
}
Then on the Controller or Action you want to use Authorization, you can use the Authorize Attribute like this
[Authorize(Roles = "Administrator")]
public void MyAction() { }

Resources