Get current windows user in Blazor Server - asp.net

I want to get the name of the current Windows user for my Blazor Server project.
I tried it via HttpContext, which is unreliable, according to this github issue.
Then I went with the MS documentation, without success.
Still returned null for User
At this point I'm wondering if it's my incompetence or something with my whole idea of using Blazor Server for this.
This is pretty much all the code:
#page "/"
#using System.Security.Claims
#using Microsoft.AspNetCore.Components.Authorization
#inject AuthenticationStateProvider AuthenticationStateProvider
<h3>ClaimsPrincipal Data</h3>
<button #onclick="GetClaimsPrincipalData">Get ClaimsPrincipal Data</button>
<p>#_authMessage</p>
#if (_claims.Count() > 0)
{
<ul>
#foreach (var claim in _claims)
{
<li>#claim.Type: #claim.Value</li>
}
</ul>
}
<p>#_surnameMessage</p>
#code {
private string _authMessage;
private string _surnameMessage;
private IEnumerable<Claim> _claims = Enumerable.Empty<Claim>();
private async Task GetClaimsPrincipalData()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
_authMessage = $"{user.Identity.Name} is authenticated.";
_claims = user.Claims;
_surnameMessage =
$"Surname: {user.FindFirst(c => c.Type == ClaimTypes.Surname)?.Value}";
}
else
{
_authMessage = "The user is NOT authenticated.";
}
}
There is also the part from the docu with the user.Identity.Name, but since I dont even get the claims, as of now, I am lost with what to do.
Edit 1:
Startup.cs
public class CustomAuthStateProvider : AuthenticationStateProvider
{
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "mrfibuli"),
}, "Fake authentication type");
var user = new ClaimsPrincipal(identity);
return Task.FromResult(new AuthenticationState(user));
}
}

In your .razor file:
<AuthorizeView>
Hello, #context.User.Identity?.Name!
</AuthorizeView>
Or in your code-behind:
[Inject]
AuthenticationStateProvider? AuthenticationStateProvider { get; set; }
public string? CurrentUserName { get; set; }
protected override async Task OnInitializedAsync()
{
if (AuthenticationStateProvider is not null)
{
var authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
CurrentUserName = authenticationState.User.Identity?.Name;
}
}

Related

Custom AuthenticationStateProvider in blazor project doesn't work on server side

Hi all!
I'm trying to make my custom auth mode in Blazor WebAssembly App (this is where studio creates 3 projects - client, server, shared). Idea is avoid IS4 auth and make my oun "internal" user for test purposes and understand the work of auth mech as well. I'm doing it by creating my custom AuthenticationStateProvider? like it shown in official docs. This is my AuthenticationStateProvider class:
public class CustomAuthStateProvider : AuthenticationStateProvider
{
private bool _isLoggedIn = true;
//this is a parameter defininng whether user logged in or not
//changed by reflection
public bool IsLoggedIn
{
get
{
return _isLoggedIn;
}
set
{
_isLoggedIn = value;
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
}
private static CustomAuthStateProvider _myInstance = null;
public Serilog.ILogger _logger { get; set; }
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
ClaimsIdentity identity;
Task<AuthenticationState> rez;
if (IsLoggedIn)
{
identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "User01"),
}, "Fake authentication type");
}
else
{
identity = new ClaimsIdentity();
}
var user = new ClaimsPrincipal(identity);
rez = Task.FromResult(new AuthenticationState(user));
return rez;
}
public static CustomAuthStateProvider GetMyInstance(Serilog.ILogger logger = null, string mySide = "")
{
//implementing singleton
if (_myInstance == null)
{
_myInstance = new CustomAuthStateProvider();
_myInstance._logger = logger;
}
return _myInstance;
}
}
This is how i plug it on client side (program.cs)
builder.Services.AddSingleton<AuthenticationStateProvider>(x => CustomAuthStateProvider.GetMyInstance(Log.Logger, "Client"));
This is how i plug it on server side (startup.cs)
services.AddSingleton<AuthenticationStateProvider, CustomAuthStateProvider>();
Problem:
it works fine on client side, that means i can login, logout and use AutorizeView and similar tags. But It doesnt' t work on server side, that means HttpContext.User doesn't see any user authenticated and i cant use [Authorize] and similar attributes. What i'm doing wrong? How HttpContext.User is connected to AuthenticationStateProvider in asp.net core project?
Thanks ;-)
Here's a very simple Test AuthenticationStateProvider I hashed up recently for a Server Side project.
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Blazor.Auth.Test
{
public class TestAuthenticationStateProvider : AuthenticationStateProvider
{
public TestUserType UserType { get; private set; } = TestUserType.None;
private ClaimsPrincipal Admin
{
get
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Sid, "985fdabb-5e4e-4637-b53a-d331a3158680"),
new Claim(ClaimTypes.Name, "Administrator"),
new Claim(ClaimTypes.Role, "Admin")
}, "Test authentication type");
return new ClaimsPrincipal(identity);
}
}
private ClaimsPrincipal User
{
get
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Sid, "024672e0-250a-46fc-bd35-1902974cf9e1"),
new Claim(ClaimTypes.Name, "Normal User"),
new Claim(ClaimTypes.Role, "User")
}, "Test authentication type");
return new ClaimsPrincipal(identity);
}
}
private ClaimsPrincipal Visitor
{
get
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Sid, "3ef75379-69d6-4f8b-ab5f-857c32775571"),
new Claim(ClaimTypes.Name, "Visitor"),
new Claim(ClaimTypes.Role, "Visitor")
}, "Test authentication type");
return new ClaimsPrincipal(identity);
}
}
private ClaimsPrincipal Anonymous
{
get
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Sid, "0ade1e94-b50e-46cc-b5f1-319a96a6d92f"),
new Claim(ClaimTypes.Name, "Anonymous"),
new Claim(ClaimTypes.Role, "Anonymous")
}, null);
return new ClaimsPrincipal(identity);
}
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var task = this.UserType switch
{
TestUserType.Admin => Task.FromResult(new AuthenticationState(this.Admin)),
TestUserType.User => Task.FromResult(new AuthenticationState(this.User)),
TestUserType.None => Task.FromResult(new AuthenticationState(this.Anonymous)),
_ => Task.FromResult(new AuthenticationState(this.Visitor))
};
return task;
}
public Task<AuthenticationState> ChangeUser(TestUserType userType)
{
this.UserType = userType;
var task = this.GetAuthenticationStateAsync();
this.NotifyAuthenticationStateChanged(task);
return task;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Blazor.Auth.Test
{
public enum TestUserType
{
None,
Visitor,
User,
Admin
}
}
Startup regstration:
services.AddScoped<AuthenticationStateProvider, TestAuthenticationStateProvider>();
Simple Component I add to NavMenu to switch users.
<AuthorizeView>
<Authorized>
<div class="m-1 p-1 text-white">
#user.Identity.Name
</div>
</Authorized>
<NotAuthorized>
<div class="m-1 p-1 text-white">
Not Logged In
</div>
</NotAuthorized>
</AuthorizeView>
<div class="m-1 p-3">
<select class="form-control" #onchange="ChangeUser">
#foreach (var value in Enum.GetNames(typeof(TestUserType)))
{
<option value="#value">#value</option>
}
</select>
</div>
#code {
[CascadingParameter] public Task<AuthenticationState> AuthTask { get; set; }
[Inject] private AuthenticationStateProvider AuthState { get; set; }
private System.Security.Claims.ClaimsPrincipal user;
protected async override Task OnInitializedAsync()
{
var authState = await AuthTask;
this.user = authState.User;
}
private async Task ChangeUser(ChangeEventArgs e)
{
var en = Enum.Parse<TestUserType>(e.Value.ToString());
var authState = await ((TestAuthenticationStateProvider)AuthState).ChangeUser(en);
this.user = authState.User;
}
}

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>();
}

AspNet.Security.OpenIdConnect vs OAuthAuthorizationProvider

I had an app in .NET Framework in which I implemented OAuthAuthorizationServer. Now I want to upgrade my app to .NET Core 2.1, so I did some R&D and decided to use ASOS. Now the issue is I have implemented ASOS and it is working fine but I have some chunks that I can't figure out how to convert.
private Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(new GenericIdentity(context.UserName, OAuthDefaults.AuthenticationType),
context.Scope.Select(x => new Claim("claim", x)));
context.Validated(identity);
return Task.FromResult(0);
}
private Task GrantClientCredetails(OAuthGrantClientCredentialsContext context)
{
var identity = new ClaimsIdentity(new GenericIdentity(context.ClientId, OAuthDefaults.AuthenticationType),
context.Scope.Select(x => new Claim("claim", x)));
context.Validated(identity);
return Task.FromResult(0);
}
private readonly ConcurrentDictionary<string, string> _authenticationCodes =
new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
private void CreateAuthenticationCode(AuthenticationTokenCreateContext context)
{
context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));
_authenticationCodes[context.Token] = context.SerializeTicket();
}
private void ReceiveAuthenticationCode(AuthenticationTokenReceiveContext context)
{
string value;
if (_authenticationCodes.TryRemove(context.Token, out value))
{
context.DeserializeTicket(value);
}
}
private void CreateRefreshToken(AuthenticationTokenCreateContext context)
{
context.SetToken(context.SerializeTicket());
}
private void ReceiveRefreshToken(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
Now I have couple of question:
Client Credentials and Resource owner password grant types are two different grant types so how can we differentiate in them using ASOS?
GrantResourceOwnerCredentials takes OAuthGrantResourceOwnerCredentialsContext as a param and GrantClientCredentials takes OAuthGrantClientCredentialsContext as a param. Both these contexts contains scope which is not available in ASOS.
How can I serialize and deserialize access and refresh tokens like I was doing OAuthAuthorizationProvider?
How do we handle refresh tokens in ASOS? I can see refresh tokens in response but I haven't write any logic for refresh token my self.
Client Credentials and Resource owner password grant types are two different grant types so how can we differentiate in them using ASOS?
public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
if (context.Request.IsClientCredentialsGrantType())
{
// ...
}
else if (context.Request.IsPasswordGrantType())
{
// ...
}
else
{
throw new NotSupportedException();
}
}
GrantResourceOwnerCredentials takes OAuthGrantResourceOwnerCredentialsContext as a param and GrantClientCredetails takes OAuthGrantClientCredentialsContext as a param. Both these contexts contains scope which is not available in ASOS
public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
var scopes = context.Request.GetScopes();
// ...
}
How can I serialize and deserialize access and refresh tokens like I was doing OAuthAUthorizationProvider?
By using the OnSerializeAccessToken/OnDeserializeAccessToken and OnSerializeRefreshToken/OnDeserializeRefreshToken events.
How do we handle refresh tokens in ASOS? I can see refresh tokens in response but I haven't write any logic for refresh token my self.
Unlike Katana's OAuth server middleware, ASOS provides default logic for generating authorization codes and refresh tokens. If you want to use implement things like token revocation, you can do that in the events I mentioned. Read AspNet.Security.OpenIdConnect.Server. Refresh tokens for more information.
Here's an example that returns GUID refresh tokens and stores the associated (encrypted) payload in a database:
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace AuthorizationServer
{
public class MyToken
{
public string Id { get; set; }
public string Payload { get; set; }
}
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options) { }
public DbSet<MyToken> Tokens { get; set; }
}
public class MyProvider : OpenIdConnectServerProvider
{
private readonly MyDbContext _database;
public MyProvider(MyDbContext database)
{
_database = database;
}
public override Task ValidateTokenRequest(ValidateTokenRequestContext context)
{
if (!context.Request.IsPasswordGrantType() && !context.Request.IsRefreshTokenGrantType())
{
context.Reject(error: OpenIdConnectConstants.Errors.UnsupportedGrantType);
}
else
{
// Don't enforce client authentication.
context.Skip();
}
return Task.CompletedTask;
}
public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
if (context.Request.IsPasswordGrantType())
{
if (context.Request.Username == "bob" && context.Request.Password == "bob")
{
var identity = new ClaimsIdentity(context.Scheme.Name);
identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.Subject, "Bob"));
var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), identity.AuthenticationType);
ticket.SetScopes(OpenIdConnectConstants.Scopes.OfflineAccess);
context.Validate(ticket);
}
else
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The username/password couple is invalid.");
}
}
else
{
var token = await _database.Tokens.FindAsync(context.Request.RefreshToken);
_database.Tokens.Remove(token);
await _database.SaveChangesAsync();
context.Validate(context.Ticket);
}
}
public override async Task SerializeRefreshToken(SerializeRefreshTokenContext context)
{
context.RefreshToken = Guid.NewGuid().ToString();
_database.Tokens.Add(new MyToken
{
Id = context.RefreshToken,
Payload = context.Options.RefreshTokenFormat.Protect(context.Ticket)
});
await _database.SaveChangesAsync();
}
public override async Task DeserializeRefreshToken(DeserializeRefreshTokenContext context)
{
context.HandleDeserialization();
var token = await _database.Tokens.FindAsync(context.RefreshToken);
if (token == null)
{
return;
}
context.Ticket = context.Options.RefreshTokenFormat.Unprotect(token.Payload);
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(options =>
{
options.UseInMemoryDatabase(nameof(MyDbContext));
});
services.AddAuthentication()
.AddOpenIdConnectServer(options =>
{
options.TokenEndpointPath = "/token";
options.ProviderType = typeof(MyProvider);
options.AllowInsecureHttp = true;
})
.AddOAuthValidation();
services.AddMvc();
services.AddScoped<MyProvider>();
}
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseMvcWithDefaultRoute();
}
}
}

How to add roles to users in ASP.NET with OWIN

I'm new in ASP.NET and I'm making a simple project to practice. I started with a simple MVC project without authentication because it adds many code that I didn't understand.
But now I want to add a membership to my sistem so I followed this guide:
http://benfoster.io/blog/aspnet-identity-stripped-bare-mvc-part-1
http://benfoster.io/blog/aspnet-identity-stripped-bare-mvc-part-2
But I don't know where I can add a role to the user entity..
My Startup class is this:
public class Startup
{
public static Func<UserManager<Usuario>> UserManagerFactory { get; private set; }
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "ApplicationCookie"
});
UserManagerFactory = () =>
{
var usermanager = new UserManager<Usuario>(new UserStore<Usuario>(new Vetpet3Context()));
usermanager.UserValidator = new UserValidator<Usuario>(usermanager)
{
AllowOnlyAlphanumericUserNames = false
};
return usermanager;
};
}
}
And I'm creating (for now) my users with this Action:
[HttpPost]
public async Task<ActionResult> Register(RegisterModel model)
{
if (!ModelState.IsValid)
{
return View();
}
var user = new Usuario
{
UserName=model.correo,
correo = model.correo
};
var result = await userManager.CreateAsync(user, model.password);
if (result.Succeeded)
{
await SignIn(user);
return RedirectToAction("index", "Home");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error);
}
return View();
}
So everything works fine at this point, but I need to add different roles to the users , and this is the question, how I implement this part? I've read many guides but everyone does different things and I'm not sure how to add a role to my new users and how to claim these roles when they log in
to add role to the user after he successfully register :
if (result.Succeeded)
{
var roleResult = userManager.AddToRole(user.Id, "Admin");
await SignIn(user);
return RedirectToAction("index", "Home");
}
to check if the user in role :
userManager.IsInRole(user.Id, "Admin");
or even simpler in any ASP.NET MVC [Authorized] controller :
User.IsInRole("roleName");

a request level singleton object in asp.net

I trying to write a kind of pseudo singleton implementation. I want it to work similar to how HttpContext does work, where I can get an instance to the context doing something as simple as:
var ctx = HttpContext.Current;
So my implementation goes something like this:
public class AppUser
{
public string Username { get; set; }
public string[] Roles { get; set; }
public AppUser()
{
var appuser = HttpContext.Session["AppUser"] as AppUser;
if(appuser == null)
throw new Exception("User session has expired");
Username = appuser.Username;
Roles = appuser.Roles;
}
}
public class WebAppContext
{
const string ContextKey = "WebAppContext";
WebAppContext() { } //empty constructor
public static WebAppContext Current
{
get
{
var ctx = HttpContext.Current.Items[ContextKey] as WebAppContext;
if(ctx == null)
{
try
{
ctx = new WebAppContext() { User = new AppUser() };
}
catch
{
//Redirect for login
}
HttpContext.Current.Items.Add(ContextKey, ctx);
}
return ctx;
}
}
public AppUser User { get; set; }
}
And I try to consume this object as follows:
var appuser = WebAppContext.Current.User;
Now does the above line guarantee I get the user associated with the correct request context; not some other user which is associated with another concurrent http request being processed?
Apart from the fact that I can't understand why would you need to barely copy the user information from the Session container to the Items container, the answer to your question should be - yes, if the Session data is correct then the same data will be available from your static property.
I wrote a blog entry on that once
http://netpl.blogspot.com/2010/12/container-based-pseudosingletons-in.html

Resources