Skip to content

More Detailed Writeup on "Customize the User" section? #19803

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
taylorchasewhite opened this issue Sep 8, 2020 · 7 comments · Fixed by #19844
Closed

More Detailed Writeup on "Customize the User" section? #19803

taylorchasewhite opened this issue Sep 8, 2020 · 7 comments · Fixed by #19844
Assignees
Labels
Blazor Pri2 Source - Docs.ms Docs Customer feedback via GitHub Issue

Comments

@taylorchasewhite
Copy link

taylorchasewhite commented Sep 8, 2020

Background to this question

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.

Customize The User Section

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:

  • Do I ever access this CustomUserAccount at any point after adding claims in AccountClaimsPrincipalFactory<CustomUserAccount>?
  • 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?

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


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

@dotnet-bot dotnet-bot added ⌚ Not Triaged Blazor Source - Docs.ms Docs Customer feedback via GitHub Issue labels Sep 8, 2020
@guardrex guardrex self-assigned this Sep 8, 2020
@guardrex guardrex added this to the Backlog milestone Sep 8, 2020
@guardrex
Copy link
Collaborator

guardrex commented Sep 9, 2020

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.

For a component scenario

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.

User claims

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.

Scheduling

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.

Note on Identity v2.0

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 #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.

@guardrex guardrex added the Pri2 label Sep 9, 2020
@guardrex guardrex modified the milestones: Backlog, 2020 Q4 ends Dec 31 Sep 9, 2020
@guardrex
Copy link
Collaborator

guardrex commented Sep 9, 2020

oh ... and for your questions ... let me get back to you shortly on that.

@guardrex
Copy link
Collaborator

guardrex commented Sep 9, 2020

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 CustomUserAccount at any point after adding claims in AccountClaimsPrincipalFactory<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! 😄). For keying between AAD and your dB, I think it would be on the oid claim value in single tenant scenarios. See 👉 https://docs.microsoft.com/azure/active-directory/develop/id-tokens#payload-claims

@taylorchasewhite
Copy link
Author

taylorchasewhite commented Sep 10, 2020

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!).

2020-09-09 18_51_54- Blazor

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.

@taylorchasewhite
Copy link
Author

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.

@guardrex
Copy link
Collaborator

guardrex commented Sep 10, 2020

This will be a TL;DR set of remarks for many. I'm organizing my thoughts for updates ... rubber 🦆 to the rescue. 😄

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 ...

  • As you show, in the component with an access token. I'll expand that section in the doc.
  • The other way tho is customize the user with a call for any user that makes a single Graph API call up-front ... get their data into claims. That's what the current, live AAD groups and roles topic does. I won't broach the concept of safely storing data in local storage. It's possible but challenging from a security perspective to pull off. I think there are some 3rd party packages written by security gurus that can do that. We'll stick with claims unless/until the product unit decides differently.

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! 🚀 ... 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 ...

  • Get an access token client-side and make the call. Do it in the component or for the user generally and keep the data in claims.
  • Have the client-side app call a server web API app (using an access token and a secure channel ... just like our examples today for weather data in these topics). The web API calls storage (a dB or service ... Graph API ... or whatever) and then returns the data to the client.

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 🦆 work prior to actually tackling the docs.

@guardrex
Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Blazor Pri2 Source - Docs.ms Docs Customer feedback via GitHub Issue
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

3 participants