Using Identity.Web in Blazor Server [closed] - azure-active-directory

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
So I am working on a Blazor Server project that will use AzureAD B2C for identity. I have successfully configured the application and login/out/register all work correctly. The question that I have concerns using the returned user data in the application. In particular, I would like to access the Display Name and UserID data in order to link the user to data in my DB. What is the best method of accessing this data? I'm used to MVC where there is a User object that contains this data, but I'm not sure how to access that data using the new platform. I'm sure I've missed something simple.
Another question would be how to structure a link to the edit profile and reset password functions. There are plenty of examples on login/out, but not the rest of the functions.
I've included a link to my repo below. The relevant code is in the GameManager.WebApp project.
Thanks in advance for the help.
Project Repo

To get the Display name in Azure AD B2C you need to get it as an optional claim. If you are working with the built in userflow select the user flow and go to user attribute there we can see the optional claims like below
In the Blazor server application we get the claims information in the #context.User.
The user id you can get it from the id token with in the sub claim. To get the token you need to register OIDC middleware inside ConfigureServices and set SaveTokens to true :
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.SaveTokens = true;
});
And refer to this code sample : https://stackoverflow.com/a/59901672/5751404 to save tokens to localstorage for later use .
Please reference this sample for Blazor Server apps that use EF Core.

Based on that feedback, I have created a solution that works well for me. I started by adding authentication in Startup.cs which takes the connection options from appsettings.json
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
Configuration.Bind("AzureAdB2C", options);
options.SaveTokens = true;
});
I then added an ApplicationUser.cs POCO to hold the information that I was interested in for my users
public class ApplicationUser
{
public string UserId { get; set; }
public string EmailAddress { get; set; }
public string DisplayName { get; set; }
public string Country { get; set; }
}
To populate that user object, I created a _Host.cshtml.cs file in the Pages folder with the following:
public class HostAuthenticationModel : PageModel
{
public IActionResult OnGet()
{
if (User.Identity.IsAuthenticated)
{
ClaimsPrincipal user = HttpContext.User;
ApplicationUser.UserId = user.Claims
.Where(claim => claim.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier")
.FirstOrDefault().Value.ToString();
ApplicationUser.Country = user.Claims
.Where(claim => claim.Type == "country")
.FirstOrDefault().Value.ToString();
ApplicationUser.EmailAddress = user.Claims
.Where(claim => claim.Type == "emails")
.FirstOrDefault().Value.ToString();
ApplicationUser.DisplayName = user.Claims
.Where(claim => claim.Type == "name")
.FirstOrDefault().Value.ToString();
}
return Page();
}
public ApplicationUser ApplicationUser { get; set; } = new ApplicationUser();
}
This code gets the User from the HttpContext and maps the claims to my user object class. This could be extended as needed.
With that in place, I added a model directive to the top of the _Host.cshtml file
#model HostAuthenticationModel
and added a tag helper to the Component
<component
type="typeof(App)"
render-mode="ServerPrerendered"
param-ApplicationUser="Model.ApplicationUser"
/>
That tag helper passes the new ApplicationUser object into the app component as a parameter.
To use that parameter, I needed to get it inside of the App component as follows
#code {
[Parameter]
public ApplicationUser ApplicationUser { get; set; }
}
I then created a cascading parameter with that value in App so I can easily access it from any child component
<CascadingValue Value="#ApplicationUser">
<Router AppAssembly="#typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="#typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingValue>
Finally, in the component where I needed the User object, you simply declare the cascading parameter thusly
[CascadingParameter] ApplicationUser ApplicationUser { get; set; }
It is probably possible to add the ApplicationUser object into a state service of some sort and provide that through DI, but I haven't taken it to that extreme just yet (but probably will in the future at some point).

Related

Strange schema naming with EF core code-first

I'm having a weird issue that I've never seen before, with schema naming using EF core code-first.
I just created a new class LogEntry used to log SMS and emails sent to our users.
public class LogEntry
{
public LogEntry(Guid id)
{
Id = id;
}
public Guid Id { get; set; }
public Provider Provider { get; set; } // Enum
public string Content { get; set; }
public string Recipient { get; set; }
...
}
I then added configuration in my database context class, in OnModelCreating(modelBuilder modelBuilder)
public virtual DbSet<LogEntry> Log { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<LogEntry>(entity =>
{
entity.HasKey(log => log.Id);
entity.HasIndex(log => log.Date);
entity.HasIndex(log => log.Provider);
entity.HasIndex(log => log.Recipient);
...
});
}
Then I ran dotnet ef migrations add SomeMigration to actually add the migration. I setup auto migration so it automatically updates my database when the project launches. So far so good.
Now, once I went to check out the new tables it created, I made a weird naming convention regarding the databae schema.
My IIS website application pool is running with a specific managed AD user, let's call it msvc-log-api
I'm used to EF always using the dbo schema, as it default to that schema, but for some weird reason, EF decided to create a new schema named after my managed AD user mydomain\msvc-log-api$. This means that my tables are named in the following way:
mydomain\msvc-log-api$.__EFMigrationHistory
mydomain\msvc-log-api$.Log
Any idea why this is happening, and do I really need to add modelBuilder.HasDefaultSchema("dbo") to mitigate this issue?

Change SQL Server Connection String Dynamically inside an ASP.Net Core application

I open one database at the start, then need to open another database based on user selecting two values. The database selection has to be at run-time and will change every time.
Have tried to access the Connection String using the Connection String class and have tried other options like Singleton which I do not understand. I am running this on a local Windows 10 system running SQL Server Express. Am coding using Asp.Net Core 2.1
> ASP.Net Core v2.1
Building multi tenant, multi year application
Every client will have one SQL DATABASE per year
I hope to have a table with the following structure
COMPANY_CODE VARCHAR(3),
COMPANY_YEAR INT,
COMPANY_DBNAME VARCHAR(5)
Sample Data
COMPANY_CODE: AAD
COMPANY_YEAR: 19
COMPANY_DB: AAD19
COMPANY_CODE: AAD
COMPANY_YEAR: 18
COMPANY_DB: AAD18
COMPANY_CODE: AAD
COMPANY_YEAR: 17
COMPANY_DB: AAD17
So, every company will multiple rows - one for each financial year.
The COMPANY_DB column will store the DB name to open for that session.
Once the user is authenticated, I want to change the connection string to point to the database in the COMPANY_DB column of the selected row and then let the logged in user perform transactions.
I am unable to figure out how to change the connection string that is embedded in startup.cs.
Any tips on how to achieve this will be most appreciated.
I figured out that you are using one DbContext class for each database. See here for more information: docs.
Remove AddDbContext from Startup, remove OnConfiguring from DbContext and pass options to the constructor.
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
public DbSet<Blog> Blogs { get; set; }
}
Then, write service providing DbContext:
public interface IBlogContextProvider
{
BlogContext GetBlogContext(string connectionString);
}
public class BlogContextProvider : IBlogContextProvider
{
BlogContext GetBlogContext(string connectionString)
{
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connectionString);
return new BlogContext(optionsBuilder);
}
}
Add service in your Startup.cs:
services.AddScoped<IBlogContextProvider, BlogContextProvider>();
Now you can use DI:
public class HomeController : Controller
{
private IBlogContextProvider _provider;
public HomeController(IBlogContextProvider provider)
{
_provider = provider;
}
public ActionResult Index()
{
using (var context = _provider.GetBlogContext(<your connection string>))
{
//your code here
}
return View();
}
}
EDIT: Of course, you can write ContextProvider as generic.

IdentityServer 4 Restfull Login/Logout

Been looking into the identity server 4 solution to compliment my ASP CORE api.
Using a SPA page on front end, does IdentityServer4 have the capability to manage restfull calls for login/logout/other?
Currently my solution works perfectly to redirect to and from the IdentityServer4 solution, but wondering if i can improve on UX by avoiding the redirects that occur on login/logout?
I've heard of PopUp and iFrame capability, but from research that opens up other risks.
(not sure if this question is for stackoverflow or software engineering stack, happy to move it)
You may do this by using the resource owner password grant type, where you could provide your own login screen and pass the information to IdentityServer.
In IdentityServer you would implement the IResourceOwnerPasswordValidator interface to validate the users.
In your Startup.ConfigureServices add the following.
Services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
Here is a sample ResourceOwnerPasswordValidator class.
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
private IUserManager _myUserManager { get; set; }
public ResourceOwnerPasswordValidator(IUserManager userManager)
{
_myUserManager = userManager;
}
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
var user = await _myUserManager.Find(context.UserName, context.Password);
if (user != null)
{
context.Result = new GrantValidationResult(
subject: user.USER_ID,
authenticationMethod: "custom",
claims: await _myUserManager.GetClaimsAsync(user));
}
else
{
context.Result = new GrantValidationResult(
TokenRequestErrors.InvalidRequest,
errorDescription: "UserName or Password Incorrect.");
}
}
}
The IUserManager implements the logic to check the database to validate the user.
Then the SPA client would use the GrantTypes.ResourceOwnerPassword. Here is an example you could start with.
DISCLAIMER
This is not the recommended flow to use.

EntityFramework unit test example

I just solved my own question, but thought I'd might still be helpful for others to read so decided to post it anyway.
I am trying to get started with azure development and am currently at the stage of getting the database up and running. After a few hickups I achieved the following:
installed VS2012, MSSQLSERVER2012, Azure SDK .NET, EntityFramework 6.0.0 alpha and a bunch of other things
wrote my first entities (code first) and generated a database out of it.
The last thing I'd like to see before I pick up the next challenge is to actually add something to my newly created database first. I'd thought the easiest way would be writing a test in nunit.
Here's what I got so far...
The entity class User:
namespace Models.Users
{
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string EmailAddress { get; set; }
}
}
The entity class UsersDb:
using System.Data.Entity;
namespace Models.Users
{
public class UsersDb : DbContext
{
public DbSet<User> Users { get; set; }
}
}
Generated the database with the following PS commands:
enable-migrations -ProjectName Models -ContextTypeName Models.Users.UsersDb
add-migration -ProjectName Models Initial
update-database -ProjectName Models
Finally, I wrote the following unit test
using Models.Users;
using NUnit.Framework;
namespace Tests
{
[TestFixture]
public class DatabaseTests
{
[Test]
public void AddUserTest()
{
var users = new UsersDb();
var user = new User
{
Id = 1,
Name = "test",
EmailAddress = "test#gmail.com"
};
users.Users.Add(user);
users.SaveChanges();
}
}
}
That test runs, but throws an exception I can't figure out.
System.InvalidOperationException : The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' for the 'System.Data.SqlClient' ADO.NET provider could not be loaded. Make sure the provider assembly is available to the running application. See http://go.microsoft.com/fwlink/?LinkId=260882 for more information.
Solution
What I had to do to solve this, is open NuGet management (right click solution) and press the manage button on EntityFramework. In the dialog add a checkbox in front of your test solution, rebuild and go.
Now, I have a very small solution that creates a new user via a unit test and saves it into my database. A nice startup project which I can now start extending.
Solved the question while typing the question itself. Thought i'd still be useful as a reference for others.

Having a problem with RequiresRole attribute on RIA Domain service

My question is similar to this question. I hope I can provide some more detail and context to get it answered.
So here's some context: I have a simple in-house silverlight (ver 4) app with WCF Ria services that I'm building for our small support team. It uses authentication against a third-party vended database, but all other user information, e.g. FriendlyName and Roles (only 1 role per user) comes from our own database. I'm trying to keep this simple and don't want to implement custom membership and role providers.
I have few domain service operations that I want to restrict to certain roles, so I tried using the RequiresRole attribute like so:
[RequiresRole("Admin", "HelpDesk", "Billing" )]
public RisStudyInfo GetStudyInfo(string accession) {
return ris.GetStudyInfo(accession);
}
On the client side WebContext.Current.User.IsInRole("Admin") returns true, but I always get access denied when calling the service. The RequiresAuthentication attribute works as expected.
Below is the implementation of my AuthenticationService. The User class simply inherits from UserBase and adds the FriendlyName property. Any ideas what I'm doing wrong?
[EnableClientAccess]
public class AuthenticationService : AuthenticationBase<User> {
UserDataService userData = new UserDataService();
protected override bool ValidateUser(string userName, string password) {
var auth = new DatabaseAuthenticator();
return auth.Authenticate(userName, password);
}
protected override User GetAuthenticatedUser(IPrincipal principal) {
User user = null;
if (principal.Identity.IsAuthenticated) {
user = new User();
user.FriendlyName = userData.GetFriendlyName(principal.Identity.Name);
user.Name = principal.Identity.Name;
user.Roles = GetRolesFor(user.Name);
}
return user;
}
private IEnumerable<string> GetRolesFor(string username) {
IList<string> roles = new List<string>();
string role = userData.GetRolesFor(username);
if (role != null)
roles.Add(role);
return roles;
}
Figured it out. At least 2 things wrong. First clue found here. The second clue here
1.Turns out I really do need to write a custom role provider. Only need to implement GetRolesForUser though.
public override string[] GetRolesForUser(string username) {
return new string[] { _userService.GetRolesFor(username) };
}
2.Configure the custom role provider correctly in the web.config
<roleManager cacheRolesInCookie="true" enabled="true" defaultProvider="MyRoleProvider">
<providers>
<add name="MyRoleProvider" type="MyProject.Web.Providers.MyRoleProvider, MyProject.Web"/>
</providers>
</roleManager>
I solved this one by using the local credential store to cache credentials. Whenever a local cred check fails a foreign check occurs and the cache is populated/updated. This was a trivial override of the ValidateUser method. It does mean that stale passwords continue to work until the updated password is used (it will fail locally, pass remotely and trigger an update).
This approach meant that internally everything worked as per an out of the box configuration with no need for any other mods (apart from removing the local create-a-user links).

Resources