Customizing the AuthenticationStateProvider in Blazor Server App with Jwt Token Authentication - asp.net-core

I've noticed that many developers subclass the AuthenticationStateProvider both in
Blazor Server App and Blazor WebAssembly App wrongly, and more imprtantly for the wrong
reasons.
How to do it correctly and when ?

First off, you do not subclass the AuthenticationStateProvider for the sole purpose of
adding claims to the ClaimPrincipal object. Generally speaking, claims are added after a
user has been authenticated, and if you need to inspect those claims and tranform them, it
should be done somewhere else, not in the AuthenticationStateProvider object. Incidentally, in
Asp.Net Core there are two ways how you can do that, but this merits a question of its own.
I guess that this code sample led many to believe that this is the place to add claims to the ClaimsPrincipal object.
In the current context, implementing Jwt Token Authentication, claims should be added
to the Jwt Token when it is created on the server, and extracted on the client when required,
as for instance, you need the name of the current user. I've noticed that developers save
the name of the user in the local storage, and retrieved it when needed. This is wrong.
You should extract the name of the user from the Jwt Token.
The following code sample describes how to create a custom AuthenticationStateProvider object
whose objective is to retrieve from the local storage a Jwt Token string that has newly added,
parse its content, and create a ClaimsPrincipal object that is served to interested
parties (subscribers to the AuthenticationStateProvider.AuthenticationStateChanged event)
, such as the CascadingAuthenticationState object.
The following code sample demonstrates how you can implement a custom
authenticationstateprovider properly, and for good reason.
public class TokenServerAuthenticationStateProvider :
AuthenticationStateProvider
{
private readonly IJSRuntime _jsRuntime;
public TokenServerAuthenticationStateProvider(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
public async Task<string> GetTokenAsync()
=> await _jsRuntime.InvokeAsync<string>("localStorage.getItem", "authToken");
public async Task SetTokenAsync(string token)
{
if (token == null)
{
await _jsRuntime.InvokeAsync<object>("localStorage.removeItem", "authToken");
}
else
{
await _jsRuntime.InvokeAsync<object>("localStorage.setItem", "authToken", token);
}
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var token = await GetTokenAsync();
var identity = string.IsNullOrEmpty(token)
? new ClaimsIdentity()
: new ClaimsIdentity(ServiceExtensions.ParseClaimsFromJwt(token), "jwt");
return new AuthenticationState(new ClaimsPrincipal(identity));
}
}
And here's a code sample residing in the submit button of a Login page that
calls a Web Api endpoint where the user credentials are validated, after which
a Jwt Token is created and passed back to the calling code:
async Task SubmitCredentials()
{
bool lastLoginFailed;
var httpClient = clientFactory.CreateClient();
httpClient.BaseAddress = new Uri("https://localhost:44371/");
var requestJson = JsonSerializer.Serialize(credentials, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, "api/user/login")
{
Content = new StringContent(requestJson, Encoding.UTF8, "application/json")
});
var stringContent = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<LoginResult>(stringContent, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
lastLoginFailed = result.Token == null;
if (!lastLoginFailed)
{
// Success! Store token in underlying auth state service
await TokenProvider.SetTokenAsync(result.Token);
NavigationManager.NavigateTo(ReturnUrl);
}
}
Point to note: TokenProvider is an instance of TokenServerAuthenticationStateProvider.
Its name reflects its functionality: handling the recieved Jwt Token, and providing
the Access Token when requested.
This line of code: TokenProvider.SetTokenAsync(result.Token); passes the Jwt Token
to TokenServerAuthenticationStateProvider.SetTokenAsync in which the token is sored
in the local storage, and then raises AuthenticationStateProvider.AuthenticationStateChanged
event by calling NotifyAuthenticationStateChanged, passing an AuthenticationState object
built from the data contained in the stored Jwt Token.
Note that the GetAuthenticationStateAsync method creates a new ClaimsIdentity object from
the parsed Jwt Token. All the claims added to the newly created ClaimsIdentity object
are retrieved from the Jwt Token. I cannot think of a use case where you have to create
a new claim object and add it to the ClaimsPrincipal object.
The following code is executed when an authenticated user is attempting to access
the FecthData page
#code
{
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
var token = await TokenProvider.GetTokenAsync();
var httpClient = clientFactory.CreateClient();
httpClient.BaseAddress = new Uri("https://localhost:44371/");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, $"api/WeatherForecast?startDate={DateTime.Now}"));
var stringContent = await response.Content.ReadAsStringAsync();
forecasts = JsonSerializer.Deserialize<WeatherForecast[]>(stringContent, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
}
}
Note that the first line of code: var token = await TokenProvider.GetTokenAsync(); retrieves
the Jwt Token stored in the local storage, and add it to the Authorization header of the request.
Hope this helps...
Edit
Note: ServiceExtensions.ParseClaimsFromJwt is a method that gets the Jwt token extracted from the local storage, and parse it into a collection of claims.
Your Startup class should be like this:
public void ConfigureServices(IServiceCollection services)
{
// Code omitted...
services.AddScoped<TokenServerAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<TokenServerAuthenticationStateProvider>());
}

Related

How to use Auth0 for full signup-signin process

I am currently having troubles using auth0.com to set up whole authentication process for my asp.net web api project (I didn't write any view part, cause I'm using it only to learn auth/authoriz).
Their quickstart guides and docs are starting from obtaining a token for your application, I don't understand what is this token, does it grants an access to whole application or what? I wrote a default implementation with creating a user as an object, then generating a token and assigning it to user, then you pass user's email and password and log. I want to do the same using auth0.com
Is there a COMPLETE step-by-step guide on using auth0.com, with the explanation on how to create a user, how to let user log in etc.?
My default implementation:
private readonly UserManager<AppUser> _userManager;
private readonly TokenService _tokenService;
public AccountController(UserManager<AppUser> userManager, TokenService tokenService)
{
_tokenService = tokenService;
_userManager = userManager;
}
[AllowAnonymous]
[HttpPost("login")]
public async Task<ActionResult<UserDTO>> Login(LoginDTO loginDTO)
{
var user = await _userManager.FindByEmailAsync(loginDTO.Email);
if (user is null) return Unauthorized();
var result = await _userManager.CheckPasswordAsync(user, loginDTO.Password);
if (result)
{
return CreateUserObject(user);
}
return Unauthorized();
}
[AllowAnonymous]
[HttpPost("register")]
public async Task<ActionResult<UserDTO>> Register(RegisterDTO registerDTO)
{
if (await _userManager.Users.AnyAsync(x => x.UserName == registerDTO.Username))
{
ModelState.AddModelError("username", "Username taken");
return ValidationProblem();
}
if (await _userManager.Users.AnyAsync(x => x.Email == registerDTO.Email))
{
ModelState.AddModelError("email", "Email taken");
return ValidationProblem();
}
var user = new AppUser
{
DisplayName = registerDTO.DisplayName,
Email = registerDTO.Email,
UserName = registerDTO.Username
};
var result = await _userManager.CreateAsync(user, registerDTO.Password);
if (result.Succeeded)
{
return CreateUserObject(user);
}
return BadRequest(result.Errors);
}
[Authorize]
[HttpGet]
public async Task<ActionResult<UserDTO>> GetCurrentUser()
{
var user = await _userManager.FindByEmailAsync(User.FindFirstValue(ClaimTypes.Email));
return CreateUserObject(user);
}
private UserDTO CreateUserObject(AppUser user)
{
return new UserDTO
{
DisplayName = user.DisplayName,
Image = null,
Token = _tokenService.CreateToken(user),
Username = user.UserName
};
}
In general, you don't need to set up a sign-in/sign-up infrastructure with Auth0. The platform provides you with the Universal Login page where users can register or log in to your application.
The result of the authentication on the Auth0 side is one or two tokens that tell you some info about the user (ID token) and optionally what the user/application is allowed to do (access token).
To learn more about these tokens and the difference between them, read this article.
In your case, since your application is an API, you don't have to deal with user authentication directly. Your API isn't meant for users but for client applications.
To manage users, you can do it through the Auth0 Dashboard. If you want to create your own dashboard to manage users, you can do it through the Auth0 Management API. This is the library to use for .NET.
You assume that a client will call your API endpoints with the proper authorization expressed by an access token.
Take a look at this article for a basic authorization check for ASP.NET Core Web APIs, and this one for a permission-based approach. The articles also show how to test your protected API.
I hope these directions may help.

Blazor WASM Authentication and Authorization on Components and Controllers

I am developing a Blazor WASM with authentication and authorization. The idea is that the user need to login in order to be able to view the Components of the Client Project but also to be able to consume data of Controllers from Server Project which are behind the /api.
Currently I have implemented the restriction on Client components:
<AuthorizeView>
<NotAuthorized>
<div class="row">
<div class="col-md-4">
<p>Please sign in to use the Platform...</p>
</div>
</div>
</NotAuthorized>
<Authorized>
#Body
</Authorized>
</AuthorizeView>
I have also a Login and a Logout Page which are storing a Cookie for later use and perform a custom AuthenticationStateProvider
await LocalStorage.SetItemAsync<int>($"{Parameters.application}_{Parameters.enviroment}_userid", authentication.user_id);
await LocalStorage.SetItemAsync<string>($"{Parameters.application}_{Parameters.enviroment}_username", authentication.user_name);
await AuthStateProvider.GetAuthenticationStateAsync();
The AuthenticationStateProvider code is the following:
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var state = new AuthenticationState(new ClaimsPrincipal());
string authcookie_name = $"{Parameters.application}_{Parameters.enviroment}_username";
string authcookie_value = await _localStorage.GetItemAsStringAsync(authcookie_name);
if (!string.IsNullOrEmpty(authcookie_value))
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Authentication, authcookie_value)
}, "Login");
state = new AuthenticationState(new ClaimsPrincipal(identity));
}
NotifyAuthenticationStateChanged(Task.FromResult(state));
return state;
}
The authentication controller is the following:
[HttpPost, Route("/api/auth/login")]
public IActionResult AuthLogin(Authentication authentication)
{
try
{
int auth = _IAuth.AuthLogin(authentication);
if (auth != -1)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Authentication, authentication.user_name)
};
var claimsIdentity = new ClaimsIdentity(claims, "Login");
var properties = new AuthenticationProperties()
{
IsPersistent = true
};
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), properties);
}
return Ok(auth);
}
catch { throw; }
}
Everything is working as excepted and the user need to login in order to see the content of the pages, but he is able to see the data of each page if he perform an http call http://domain.ext/api/model/view
In order to resolve this problem I added the Authorize attribute on each controller of Server project like this:
[Authorize]
[Route("/api/model")]
[ApiController]
public class Controller_Model : ControllerBase
{
}
And also added this code on the Program.cs of Server project in order to be able to make controller to work
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.SlidingExpiration = true;
options.LoginPath = new PathString("/auth/login");
options.LogoutPath = new PathString("/auth/logout");
options.Cookie = new CookieBuilder();
options.Cookie.MaxAge = options.ExpireTimeSpan;
options.AccessDeniedPath = "/";
options.EventsType = typeof(CustomCookieAuthenticationEvents);
});
Now the user is not able to see the content of a page even he is making a request to the /api.
The problem is that after some time, even I see the User is still logged in the Authorize attribute of controllers is consider the user not authorized and it returns an error because controller is not returning the supposed object list.
I have no clue why and when this is happening. Then if user Logout and Login again it works for a while again.
===============UPDATE 1===============
After lot of investigation, seems that the client side is authenticated and then every time it sees the localstorage item it continues to be in authenticated state. On the other side the server state is based on a cookie which expires after 30mins.
So the Client and the Server states are operated differently and that's why the Client seems authenticated while Server is not while denying access on controllers.
I think the solution is to change the CustomAuthenticationStateProvider in order to check if the cookie exists and if it's valid. So the event order be as follow:
User SingIn via Client Page -> Server Controller creates the cookie -> Client Page is authenticated via Authentication State Provider which reads the cookie.
Any ideas?
Seems that is possible to read and write cookies from Client Project only via Javascript. What needs to be done is the following:
A custom javascript file "cookie.js", under wwwroot/js:
export function get() {
return document.cookie;
}
export function set(key, value) {
document.cookie = `${key}=${value}`;
}
A C# class file "CookieStorageAccessor.cs", under /Classes:
public class CookieStorageAccessor
{
private Lazy<IJSObjectReference> _accessorJsRef = new();
private readonly IJSRuntime _jsRuntime;
public CookieStorageAccessor(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
private async Task WaitForReference()
{
if (_accessorJsRef.IsValueCreated is false)
{
_accessorJsRef = new(await _jsRuntime.InvokeAsync<IJSObjectReference>("import", "/js/cookie.js"));
}
}
public async ValueTask DisposeAsync()
{
if (_accessorJsRef.IsValueCreated)
{
await _accessorJsRef.Value.DisposeAsync();
}
}
public async Task<T> GetValueAsync<T>(string key)
{
await WaitForReference();
var result = await _accessorJsRef.Value.InvokeAsync<T>("get", key);
return result;
}
public async Task SetValueAsync<T>(string key, T value)
{
await WaitForReference();
await _accessorJsRef.Value.InvokeVoidAsync("set", key, value);
}
}
The C# class can be used injecting javascript and reading the cookie on
CustomAuthStateProvider:
//CREATE INSTANCE OF COOKIE ACCESSOR
CookieStorageAccessor cookieStorageAccessor = new CookieStorageAccessor(_jSRuntime);
//CHECK IF COOKIE IS EXISTS FROM COOKIE ACCESSOR
string auth_cookie = await cookieStorageAccessor.GetValueAsync<string>("authentication");
if (!string.IsNullOrEmpty(auth_cookie))
{ }
else
{ }

Can I access my ID Token from an AuthenticationStateProvider in my Blazor client?

I'm trying to access the ID Token provided by Azure AD. I really need to get access to the signature and all of the claims.
I can easily access the AuthenticationStateProvider and then get the User from that. The User property has all of the claims in it, but that seems to be all. I cannot figure out how to get access to the rest of the ID Token.
Here is the code I'm using in the code behind on my blazor page:
[Inject]
AuthenticationStateProvider authenticationState { get; set; }
public async Task<string> GetIDTokenAsync()
{
var authState = await authenticationState.GetAuthenticationStateAsync();
return string.Join(", ", authState.User.Claims.ToList());
}
I'm just returning the list as a string to see what data I'm getting.
Additionally, my bootstrap looks like this:
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
});
await builder.Build().RunAsync();
}
}
After I open the website I log in using the RemoteAuthenticatorView component. I need to access the ID Token after logging in. Also I just want to clarify that I am not looking for the access token. I do not ask for any scopes, and therefore there is no access token. My goal here is to get the ID Token and pass it to my API where the API can verify the ID Token and then issue it's own access token.

How to simply authenticate a request in middleware?

I am developing a Web API in ASP.NET Core 2.2. I want to authenticate every request by any user at any time based on token stored in Authorization header of HTTP Request so the user can call controllers and actions which are annotated with [AuthorizeAttribute]. This is my middleware:
public class TokenBasedAuthenticationMiddleware
{
private RequestDelegate nextDelegate;
public TokenBasedAuthenticationMiddleware(RequestDelegate next) => nextDelegate = next;
public async Task Invoke(HttpContext httpContext, IRegistrationsRepository registrationsRepository)
{
if (registrationsRepository.IsAuthorized(httpContext.Request.Headers["Authorization"]))
{
//Code To Authenticate this request?
}
await nextDelegate.Invoke(httpContext);
}
}
How can I simply authenticate the request (i.e set HttpContext.User.Identity.IsAuthenticated to true) without going into any complexity?
You can try something like :
var claims = new[] { new Claim("name", "YourName"), new Claim(ClaimTypes.Role, "Admin") };
var identity = new ClaimsIdentity(claims, "JWT");
httpContext.User = new ClaimsPrincipal(identity);
await nextDelegate.Invoke(httpContext);
But you can directly use the AddJwtBearer extension if using JWT bearer token authentication . The JwtBearer middleware looks for tokens (JSON Web Tokens or JWTs) in the HTTP Authorization header of incoming requests. If a valid token is found, the request is authorized. You then add the [Authorize] attribute on your controllers or routes you want protected:
Related code samples :
https://developer.okta.com/blog/2018/03/23/token-authentication-aspnetcore-complete-guide
https://garywoodfine.com/asp-net-core-2-2-jwt-authentication-tutorial/

Authorizing a user based on claims when using a reference token

I am using a reference token to get the users claims as the JWT token was too long. This works fine from a client controller:
var introspectionClient = new IntrospectionClient(
"http://localhost:5000/connect/introspect",
"api1",
"secret");
var response = await introspectionClient.SendAsync(
new IntrospectionRequest { Token = await HttpContext.GetTokenAsync("access_token") });
ViewBag.Json = Json(response.Json).Value;
return View("json");
As I can use HttpContext to retrieve the access token and exchange it for the users claims at the introspection endpoint.
However from the authorization handler I cannot access the HttpContext to get the access token and the AuthorizationHandlerContextonly contains claims from the id token.
I'm open to all suggestions but it does feel like there should be a way to get the access token in the authorization handler but i could well be wrong.
Thanks in advance for your time.
I do not know if this is the way you should do it. But to answer your question, you can inject the context like this:
//using Microsoft.AspNetCore.Authentication;
public class MyAuthorizationHandler : AuthorizationHandler<MyRequirement>
{
private IHttpContextAccessor _httpContextAccessor;
public MyAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected async override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
var token = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
}
}
You will need to make HandleRequirementAsync async. You don't have to add IHttpContextAccessor in startup to the services. This is already done by Identity.