I have been building an application in Blazor the past 6-9 months or so and regularly read through the documentation you guys have (you've probably seen my name before). Despite reading through much of the Blazor docs (including Luke's recent Authentication/Authorization writeup), I've very much struggled finding information/best practices on how to deal with users when you're not using Local User Accounts with Identity, but instead authenticating with AzureAD.
I also wrote a similar question on StackOverflow as I've not been able to follow this.
If I understand the phrase "users bound to the app", this seems like a good starting point detailing how to populate additional information from AzureAD like the Microsoft account profile photo and adding it as a claim for the period of the session.
What I still don't understand is:
CustomUserAccount at any point after adding claims in AccountClaimsPrincipalFactory<CustomUserAccount>?I think having a sample app like the CarChecker that specifically deals with storing information from an OIDC provider like AzureAD about users and allows them to edit their data, would be very helpful.
Thanks!
Taylor
โ Do not edit this section. It is required for docs.microsoft.com โ GitHub issue linking.
Hello @taylorchasewhite ...
how to populate additional information from AzureAD like the Microsoft account profile photo and adding it as a claim for the period of the session.
You're not asking about this here; but for the component scenario, this is the way ...
https://docs.microsoft.com/aspnet/core/blazor/security/webassembly/additional-scenarios#request-additional-access-tokens
This section probably should be expanded out with a full example to go ahead and make the Graph API call there. I'm mentioning this here to remind myself later on it.
Yes! ... you're at the right spot ...
https://docs.microsoft.com/aspnet/core/blazor/security/webassembly/additional-scenarios#customize-the-user
... because a Graph API call can be used to get the user data to create user claims that the app needs. For an example, see ...
https://docs.microsoft.com/aspnet/core/blazor/security/webassembly/azure-active-directory-groups-and-roles#user-defined-groups-and-built-in-administrative-roles
... where security group claims are established from Graph API when a hasgroups claim comes back, which indicates that the user has too many groups+roles to send back during the initial auth. The app has to make the Graph API call and create the group claims itself. You'd be doing the same thing but for other user data.
This specific case could be generalized just like the case where an individual component would make a Graph API call. That's a good idea. I agree with you.
Leave this issue open for work. However, I'm kind'a slammed for the .NET 5 release coming up at the moment. This is either something that I'll be able to squeeze in within the next few weeks ... say on a quiet day ... or it will need to be put off until after .NET 5 releases.
For .NET 5, Blazor goes to a new MSAL setup for Identity v2.0. The initial docs will be released (I hope ๐ค) during the RC1 time period. The work is tracked by https://github.com/dotnet/AspNetCore.Docs/issues/19503. I'll get on that about a week perhaps after RC1 releases. I can't say just how much of the guidance is due to change, perhaps not much for the scenarios that we're discussing on this issue. It's something to keep in mind tho because the Graph API calls might be a little different for you between v1.0 and v2.0 Identity, even if it is only the endpoint. There could be some data processing deltas that don't match what you see in the hasgroups example. I won't know until I work that issue.
oh ... and for your questions ... let me get back to you shortly on that.
ok ... actually ... I'll answer now. I thought I needed to make a call, but that can wait. I think Summit Racing lost my fuel injection spider!! ๐จ
Do I ever access this
CustomUserAccountat any point after adding claims inAccountClaimsPrincipalFactory<CustomUserAccount>?
You can get the user's claims via AuthenticationStateProvider (https://docs.microsoft.com/aspnet/core/blazor/security/#authenticationstateprovider-service), but engineering is more keen on https://docs.microsoft.com/aspnet/core/blazor/security/#expose-the-authentication-state-as-a-cascading-parameter to get the user ... thus claims.
How do I store information about this user in my local database? I.e. -- user makes a comment while logged in, but if I don't have any permanent store for this user, how am I to show their name and photo alongside their comment in my web app?
"Local" ... in a WASM app? If you mean permanent storage, you typically would have a backend dB (or a data storage service) accessed via a separate server app web API. If you really do mean "local" as in you intend to use non-permanent storage in the browser, you'd have to roll your own localStorage/sessionStorage provider or find a 3rd party package that will do it.
ADDITIONAL NOTE ... also one wouldn't typically store data that's already maintained by Azure AD. The profile pic, for example, would always come via AAD. You wouldn't normally store that in a dB. The custom data ... the user's comment that you mentioned ... yes, that would go into your dB.
_ONE MORE NOTE!!!_ (I'm on a roll here! :smile:). For keying between AAD and your dB, I think it would be on the oid claim value in single tenant scenarios. See :point_right: https://docs.microsoft.com/azure/active-directory/develop/id-tokens#payload-claims
Thanks for the detailed writeup!
I created my Blazor app following this writeup Secure an ASP.NET Core Blazor WebAssembly hosted app with Azure Active Directory, so I _didn't_ create app with the "Individual User Accounts" (image below) hosted on my .NET Core Server (that's what I meant by local--not in localStorage/sessionStorage, which would be frightening!).

What I'm struggling with the most, and at this point it may be more outside Blazor and just generally app design using Microsoft's Identity/Graph/user framework is how to model users and access their information outside of _their_ session. So when you say _not_ to store information we can grab from AAD, how does that work?
I've been reading more about the Graph APIs:
Based on what I've seen, if I wanted to do something like...show the profile picture of a user, I'd have to do all of this in a component:
@page "/"
@inject IHttpClientFactory HttpClientFactory
@inject IAccessTokenProvider TokenProvider
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you, @userDisplayName?" />
@code {
private HttpClient _httpClient;
private string userDisplayName;
protected override async Task OnInitializedAsync()
{
// 1 - Instantiate (or inject) a client
_httpClient = HttpClientFactory.CreateClient();
// 2. get a token from graph API
var tokenResult = await TokenProvider.RequestAccessToken(
new AccessTokenRequestOptions
{
Scopes = new[] { "https://graph.microsoft.com/Mail.Read",
"https://graph.microsoft.com/User.Read" }
});
if (tokenResult.TryGetToken(out var token))
{
// 3. If we get the token, add it
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token.Value);
// 4. make API call to get info about a user (logged in one, or otherwise, or other graph call)
var dataRequest = await _httpClient.GetAsync("https://graph.microsoft.com/beta/me");
if (dataRequest.IsSuccessStatusCode)
{
var userData = System.Text.Json.JsonDocument.Parse(await dataRequest.Content.ReadAsStreamAsync());
userDisplayName = userData.RootElement.GetProperty("displayName").GetString();
}
}
}
}
This just seems like so much work, when all I want is a photo, and I'd need to do this in any component I want to access graph API information?
Can I get the bearer token in the Program.Main method and add it to a "GraphAPI" http client that I can inject to any component? How exactly do you recommend folks do these reads?
In my perfect world, Graph would just work like this:
<img src="https://graph.microsoft.com/v1.0/users/nxIVBaKucZT2INgTesqNra6sTsb0wzy2RjgTDMyJTLI/photo" />
rather than needing to process JSON responses to pull info out, but maybe this is just the way it is, or there actually is an easier way that I'm not aware of since I'm just learning about Graph now.
Granted in the example above, I know that I can/should read from the httpcontext/claims of the authenticated user, but this is more generally about getting information not passed down in claims that we need to go to graph for like information about another user.
This will be a TL;DR set of remarks for many. I'm organizing my thoughts for updates ... rubber ๐ฆ to the rescue. :smile:
didn't create app with the "Individual User Accounts"
... generally app design ... how to model users and access their information ...
For Blazor Server it's easy enough: Most of the content for an ASP.NET Core app applies directly. We just refer the reader over to the main doc set.
Blazor WebAssembly is a challenge to cross-link to the main security docs because much of that content is tied directly to server-based ASP.NET Core app security concepts that aren't shared with Blazor WebAssembly. What we've been doing thus far is to call out the SPA concepts that we need on an as-called-for basis, such as via your feedback on this issue and when one of the engineers or I think of something that we should cover.
An additional action item for this issue is to add a section in the WebAssembly security overview, which focuses on standalone and hosted Blazor, that explains in a nutshell how users and their data are managed in SPA terms.
I've been reading more about the Graph APIs:
For your first link, as you can see, we don't maintain that on this docs team. It's a fine tutorial, but it's based on preview packages ... and as you say ... it's Blazor Server. I'll have to see how different the new .NET 5 Blazor Server template with security is from what they show. It's likely that I won't be keen to cross-link because tutorials are such a bear to keep current that they have periods of staleness that cause major problems for readers.
There's one really key line at the end of their tutorial of interest ...
Instead of hand rolling your Microsoft Graph HTTP requests, you should leverage the Microsoft Graph SDK which simplifies the interaction with Microsoft Graph and provides all the data objects youโll need to serialize and deserialize from. However, in this instance we went with option 1 because we only make one call to Microsoft Graph.
... which is one aspect of what to cover. For the _Server app in a hosted WASM solution_, that's what one does ... it's ADAL + Graph SDK for v1.0 today ... it will be Identity v2.0 (Microsoft.Identity.Web) and a new Graph SDK service client for .NET 5 and going forward. The current (old) ADAL+Graph SDK is what's on the new PR that I put up for a Server app that needs to get a user's AAD security groups+roles. Although that's becoming legacy, ADAL is supported until 2022 and is still widely in use. It seemed reasonable to me to start with the current release Identity (v1.0) and then produce the new content for v2.0 for .NET 5.0. The product unit seems ok with this approach thus far ... Dan hasn't issued an order calling for my head on a platter yet! ๐ฆ๐ช๐ฝ๏ธ๐จ๐ After all, the 3.x Blazor packages for Identity are based on the Identity v1.0 platform today, and the new paradigm will be based on v2.0 for .NET 5. It's ok to (quickly) cover it. I don't want to blow too much time on something becoming legacy, of course.
Side Note WRT that PR: ADAL caches the tokens. _The user's security group data is not cached or stored._ That's a developer decision to make depending on the data and security requirements. Many apps will want fresh user data all the time from Graph API. Also, why store something twice? When you get into storing data anywhere, including caching it, sure ... responsiveness often improves ... but one runs the risk of _stale data_ and data that's _costly_ ๐ฐ to maintain, both because that code has to be developed and maintained and because additional databases and services cost ๐ฒ๐ต๐ต๐ต๐ฒ. Because that PR is working with AAD security groups+roles, I feel that the data should always be fresh and not ever stored. What if a user's AAD security groups change? We don't want that user to continue using unauthorized services on the basis of stale group membership affiliations.
The AAD groups and roles topic can keep what it has. The general coverage in the Additional scenarios topic will be similar, and I think the example code should obtain some basic profile information. The overview topic should address the basics and link to the Additional scenarios coverage.
The other aspect tho is from the client-side ... a standalone WASM app (or hosted ... which is just a served standalone WASM app). If there's no web API endpoint (a server app) to get Graph API data for a user, one would want to call Graph API on the client. The approaches are ...
if I wanted to do something like...show the profile picture of a user, I'd have to do all of this in a component:
Yep ... you got it ... and something along those lines is how I plan to improve the existing section of the doc. I'll just add the extra lines that make the call and get some info.
This just seems like so much work, when all I want is a photo
I'm not aware of any cool new way to do it with the new API and SDKs. I'm not aware yet of anything. I'll research it, but I think it will be the same as it is today for v2.0 (MSAL) + the latest Graph API (referring to Graph web API ... not SDK).
Can I get the bearer token in the Program.Main method and add it to a "GraphAPI" http client that I can inject to any component?
_Houston ... we have a problem!_ :rocket: ... Access token lifetime is deliberately short for security. It's covered here at bullet 4. You have to request an access token and then make a call with it fairly quickly to access an external server API. Also, it's not just the period of time that's the issue ... the token could be revoked. In addition, there's no way to safely offer token refresh on the client. All of this adds up to a one-shot affair for access tokens. Caching, like the way that ADAL does it in server apps, isn't something that I would broach without help from the product unit. Idk today if Javier built anything like that into the Blazor framework. I don't think he did, but I'd need to look in reference source or ask him later about it. Even if it isn't baked-in but possible/reasonable for the dev to do on their own, it's likely going to be beyond the scope of what we can do in these docs today.
this is more generally about getting information not passed down in claims that we need to go to graph for like information about another user.
Options are limited in the SPA (Blazor WASM) world ...
I think I might just give this a shot _today_. It's the calm before the storm here with RC1 coming out soon. I'll be buried โฐ๏ธโ๏ธ for a little while once that comes out. I'll see about getting these updates done today and tomorrow. No promises tho. If something comes up, especially one or more calls from the product unit to work on something else, then I'll need to table this for several weeks due to the arrival of RC1.
Ok ... well this turned out to be another one of my ๐ book ๐ set of remarks ๐. This is good rubber :duck: work prior to actually tackling the docs.
Confirmed on working this now. This is a good time for this because when I get to RC1 it would be nice to have this issue resolved. Then, Identity v2.0 + new Graph API/SDK can start with a copy of this coverage just just like everything else here for the RC1 updates. I've started working on this, and I hope to have a draft PR tomorrow afternoon. I'll ping you on the PR. If it gets delayed to Monday, I'll let you know here.
Most helpful comment
ok ... actually ... I'll answer now. I thought I needed to make a call, but that can wait. I think Summit Racing lost my fuel injection spider!! ๐จ
You can get the user's claims via
AuthenticationStateProvider(https://docs.microsoft.com/aspnet/core/blazor/security/#authenticationstateprovider-service), but engineering is more keen on https://docs.microsoft.com/aspnet/core/blazor/security/#expose-the-authentication-state-as-a-cascading-parameter to get the user ... thus claims."Local" ... in a WASM app? If you mean permanent storage, you typically would have a backend dB (or a data storage service) accessed via a separate server app web API. If you really do mean "local" as in you intend to use non-permanent storage in the browser, you'd have to roll your own localStorage/sessionStorage provider or find a 3rd party package that will do it.
ADDITIONAL NOTE ... also one wouldn't typically store data that's already maintained by Azure AD. The profile pic, for example, would always come via AAD. You wouldn't normally store that in a dB. The custom data ... the user's comment that you mentioned ... yes, that would go into your dB.
_ONE MORE NOTE!!!_ (I'm on a roll here! :smile:). For keying between AAD and your dB, I think it would be on the
oidclaim value in single tenant scenarios. See :point_right: https://docs.microsoft.com/azure/active-directory/develop/id-tokens#payload-claims