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

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.

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.

ASP.NET Core 3.1 Microsoft Graph Access Token lost when application is restarted

Using ASP.NET Core 3.1 and Microsoft Graph API, when the application is restarted the Access Token is lost. The user is still logged in, but is unable to perform any Graph API calls.
I have tried the recommendations mentioned here Managing incremental consent and conditional access but was unable to refresh the token, and resume automatically.
Here is my Controller:
readonly ITokenAcquisition tokenAcquisition;
private GraphServiceClient graphServiceClient;
public HomeController(GraphServiceClient graphServiceClient, ITokenAcquisition tokenAcquisition)
{
this.graphServiceClient = graphServiceClient;
this.tokenAcquisition = tokenAcquisition;
}
[HttpGet]
[AuthorizeForScopes(Scopes = new string[] {"user.read"})]
public async Task<IActionResult> A()
{
User user;
try
{
var scopes = new string[] { "user.read" };
var accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
user = await graphServiceClient.Me.Request().GetAsync();
}
catch (MicrosoftIdentityWebChallengeUserException ex)
{
// token is invalid....
// the throw causes a redirect to the User Login Page
throw ex.MsalUiRequiredException;
}
user = await graphServiceClient.Me.Request().GetAsync();
Serilog.Log.Debug("{#User}", user);
return View();
}
In the code above, when the application is restarted the access token is
lost, and re-throwing the exception causes a redirect to the Login-Page.
If I then click the Sign-in with Microsoft button, the user is already signed-in,
there is no need to enter the credentials. If I then access the controller calling the Graph API, the API calls succeed.
How can I refresh the tokens before calling the API?
Also how can I debug this? If I set a breakpoint at throw ex.MsalUiRequiredException; It is of no use, I cannot see,
where the redirect get's it's value from.
The Annotation [AuthorizeForScopes(Scopes = new string[] {"user.read"})]
Is responsible for dealing with the redirect.
For debugging, use the AuthorizeForScopes source file.
In this specific case the AuthenticationScheme was not set to a value,
and was null.
Annotate the Action method to enforce a specific Scheme.
e.g. AuthenticationScheme=OpenIdConnectDefaults.AuthenticationScheme
[HttpGet]
[AuthorizeForScopes(Scopes = new string[] {"user.read"}, AuthenticationScheme=OpenIdConnectDefaults.AuthenticationScheme)]
public async Task<IActionResult> A()
{
// snipped
}
This resulted into the desired redirect path:
https://login.microsoftonline.com/{}/oauth2/v2.0/authorize?client_id={}

Customizing the AuthenticationStateProvider in Blazor Server App with Jwt Token Authentication

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

How can I get id_token in identity server, before redirecting to client?

After the user successfully signed in and before redirecting him to the client site I want to store the id_token in db. Actually the id_token is available in the client side but I don't know how to get it on the login process of identity server.
I would appreciate any help.
You can create a custom ITokenService by inheriting DefaultTokenService and store Id_token after creation.
public class CustomTokenService : DefaultTokenService
{
public CustomTokenService(
IClaimsService claimsProvider,
IReferenceTokenStore referenceTokenStore,
ITokenCreationService creationService,
IHttpContextAccessor contextAccessor,
ISystemClock clock,
ILogger<DefaultTokenService> logger)
: base(claimsProvider, referenceTokenStore, creationService, contextAccessor, clock, logger)
{
}
public override async Task<string> CreateSecurityTokenAsync(Token token)
{
strign jwt = await base.CreateSecurityTokenAsync(token);
// store token
return jwt;
}
}
And also you need to register CustomTokenService
builder.Services.TryAddTransient<ITokenService, CustomTokenService>();
You can use one of the built-in events : TokenIssuedSuccessEvent :
Modify your Startup.cs :
services.AddIdentityServer(options =>
{
options.Events.RaiseSuccessEvents = true;
});
Create your custom IEventSink :
public Task PersistAsync(Event evt)
{
if (evt.Id.Equals(EventIds.TokenIssuedSuccess))
{
var _test = evt as TokenIssuedSuccessEvent;
var tokens = _test.Tokens.ToList();
}
return Task.CompletedTask;
}
Then you can find id token by checking type of each tokens .
At last register the event in Startup.cs:
services.AddScoped<IEventSink, MyEventSink>();

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.