How to access Blazor WASM session storage in Program.cs? - c#

I am trying to implement a Microservices project with a Blazor WASM front end. I am using Google as my identity provider, and have successfully setup authentication for the front end.
However, with the next step I want to send over my id_token and not my access_token to my Gateway API in order to confirm the user is authenticated when they are hitting my endpoints. I've read the documentation provided by Microsoft and there is no guidance on this. https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/additional-scenarios?view=aspnetcore-5.0
I am trying to accomplish this myself by configuring the HTTP client at Program.cs, but can't figure out how to grab the id_token from the session storage.
builder.Services.AddHttpClient("GatewayApi", async client =>
{
client.BaseAddress = new Uri(builder.Configuration["GatewayServiceConfiguration:BaseUrl"]);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", GOOGLE_ID_TOKEN_GOES_HERE);
});
I've only seen Blazor session storage available via razor pages, and never in the startup itself. Any suggestions?

Related

Trouble with On-Behalf-Of flow with standalone Blazor WASM, AAD, .NET Core 6 Web API calling MS Graph

I have a standalone Blazor WASM site (client), a separate .NET 6 web API (server) with protected endpoints and I'm trying to call MS Graph from the API.
I've read just about every article I could find on the configuration required to make this work and I'm stuck with the incremental consent failing. I get the following error when trying to access a server API which uses MS Graph:
Error acquiring a token for a downstream web API - MsalUiRequiredException message is: AADSTS65001: The user or administrator has not consented to use the application with ID '[redacted]' named '[redacted]'. Send an interactive authorization request for this user and resource.
Configuration...
Created AAD app for Web API (server), added secret for Graph configuration, set the app URI and created access_as_user scope under "Expose an API" in AAD.
Added the client ID (from the following step) to the knownClientApplications section in the manifest for the server app registration in AAD.
For API Permissions I added Graph scopes User.Read, User.Read.All, and Group.Read.All and provided admin consent in the AAD UI.
Configured appsettings.json in the API to add the Graph API BaseUrl and above scopes from step 2 along with the correct AzureAD domain, TenantId, ClientId, and ClientSecret values for MSAL to function.
Configured MSAL on the server:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"))
.AddInMemoryTokenCaches();
Created AAD app for Blazor WASM, used SPA auth w/redirect to https://localhost:7014/authentication/login-callback and set the API permissions to api://[redacted]/access_as_user only.
Created custom authorization message handler according to this article.
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigation) : base(provider, navigation)
{
ConfigureHandler(
authorizedUrls: new[]
{
"https://localhost:7069"
},
scopes: new[]
{
"api://[redacted]/.default"
});
}
Configured MSAL on the client:
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://[redacted]/.default");
options.ProviderOptions.LoginMode = "redirect";
}
Set up named HTTP client on the Blazor client with custom message handler:
var baseAddress = builder.Configuration["PublicApiUrl"];
builder.Services.AddHttpClient("PublicApi", client =>
{
client.BaseAddress = new Uri(baseAddress);
}).AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("PublicApi"));
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
What works...
I can authenticate as an AAD user to the Blazor client.
I can access protected endpoints (using policy-based authorization) hosted on the server which don't have a dependency on MS Graph.
Questions...
Following this article's guidance about incremental consent, specifically the "Static permissions" section, I would assume granting admin consent for Graph on the server's app registration would suffice?
All of the documentation showing Blazor WASM with a protected API calling a protected API (Graph) assume the Blazor client is also hosted by the API server. Is it even possible to use on-behalf-of flow in my case? If it was hosted I could see the API calling the Blazor navigation subsystem to perform an incremental consent redirect but when they're separated, I can only imagine the static permissions is the way to go.
Is it necessary to set the DefaultAccessTokenScopes in the client?
The issue here is use of the AddMicrosoftGraph method when the API application is being built.
The GraphServiceClient created by AddMicrosoftGraph will have default access to delegated permissions which are assigned to users as opposed to application permissions which are assigned to applications. This is why the MsalUiRequiredException is being thrown which is usually resolved by prompting the user to login.
You can read more about delegated vs application permissions here.
What you can do instead is use the AddMicrosoftGraphAppOnly method to create a GraphServiceClient that will use credentials specific to your API to retrieve the relevant data needed from the Microsoft Graph API.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraphAppOnly(
authenticationProvider => new GraphServiceClient(authenticationProvider))
.AddInMemoryTokenCaches();
So long as you have the relevant settings and secrets provided in the AzureAd section of your appsettings.json file the GraphServiceClient injected into your application should now be able to access the data you need.
You can read more about app configuration with the AzureAd settings in your appsettings.json file here.

Utilize Dropbox in Web API

Not sure if this is really silly question, but is it possible to access / utilize the Dropbox within the scope of a web API.
Take the following into consideration. I have a multi-tenant front-end application written in Angular. The back-end (multi-tenant) in a C# web API. The user can upload documents / files. The API will be responsible for uploading the documents to the storage provider. In this case it's Dropbox. The thing is, I have read that Dropbox no longer supports username / password auth. Thus OAuth is used were by the user needs to provide consent to the calling application. This only has to happen once. Since multiple users within a single tenant can utilize the online storage, I'm struggling to see how this would work through a web API...
Any suggestions?
Thanks!
Yes, you can utilize 3rd party API inside Web API. I have never used Drop box API, but as you are saying they use OAuth as authentication mechanism i can show you the code which calls the 3rd party API in this case it is (clarifai).
i am using HttpClientyou can use Dropbox .NET SDK. However if you want to stick to HTTP then here is the documentation for the same.
string token = "Oauth token received using some mechanism";
string requestPath = string.Format("v1/tag");
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("https://api.clarifai.com/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
HttpResponseMessage response = await client.PostAsync(requestPath, content);
if (response.IsSuccessStatusCode)
{
var resString = await response.Content.ReadAsStringAsync();
// JSON Response
JObject resJsonObject = JObject.Parse(resString);
}
Now, As you have the concern to authenticate the user and get the token. Here is Dropbox example in which they show how to redirect a user on dropbox website to authenticate and get the temporary oauth token.
Take a look at Connect Action Method and AuthAsync Action Method in Home Controller.
Hope this helps you. If any doubt feel free to ask.

How to get bearer token to a aspnet razor page

I have a question about Bearer Token on asp.net WebApi. I've been creating WebApi's but just to be consumed by some client(Android,iOS), but now I need to create a Login page in this same project and I don't know how to handle this, since once using a client app, i just make a request to api/token and get the access token, but how to get this from a Web Page? Do I need to use some back-end like NodeJs or AngularJS ?
This is the endpoint :
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions()
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(300),
AllowInsecureHttp = true
});
Essentially, whatever you use on the client side to get the token (I use Angular and React as they have in built functionality for making http requests), you then need to get the response and store the token in the client. Take a read of this https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage. It should give you a bit of insight into client side storage. Again I tend to use cookies as it's built in with a lot of the .net MVC stuff, but you could use session storage also

IdentityServer3 - 401 Unauthorized when calling Identity API Controller

I have a project which uses IdentityServer3 for Auth as a Service.
Recently I was tasked with creating a seamless experience for End-Users to edit their identity information.
I did this by creating an API Controller in my application which uses a HTTPClient to call another API Controller living in my IdentityServer project. It basically exposes the Identity management methods to the world, but "passes-through" any requests on to the IdentityServer Api.
All is well right up until I call the IdentityServer Api Controller. My breakpoint there is never hit, regardless of the presence of a "Authorize" attribute. I end up receiving a "401: Unauthorized" back from the IdentityServer Api controller.
I've tried to reuse the original request's Auth headers, but that didn't work. I also tried to find a "access_token" claim from my claim principle, but one wasn't found.
here is a code snippet:
var httpClient = new HttpClient();
// this didn't work - tried reusing the auth from the original request
//httpClient.DefaultRequestHeaders.Authorization = request.Headers.Authorization;
// this didn't work either - "access_token" is not found
//httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", Caller.FindFirst("access_token").Value);
var routePrefix = GetRoutePrefix();
var response = await httpClient.PostAsync(
$"{routePrefix}/post",
new ObjectContent(typeof(TDObj), entity, new JsonMediaTypeFormatter()));
return response;
I'm new to IdentityServer3 and OAuth and not sure what to do next. I tried creating a new scope for "identity" and tried to make it a required scope in my client application, but that didn't seem to do the trick. I know I'm missing some key piece of understanding here, but there is so much documentation for IdentityServer, I don't know where to begin and can't find anything specific to this need. I'm in the weeds! Can anyone help me understand what's going on here? Thanks!
I got it working by following Scott Brady's answer here: Identity Server and web api for user management
However, his answer didn't immediately work for me. I had to make sure to make the call for UseIdentityServerTokenValidation to happen BEFORE api route mapping happened.
That being said, my original attempt to hi-jack the Authorization headers from the inbound HTTPRequest of my Front-End application worked, so I was able to remove any code requesting an access token and didn't have to SetBearerToken() on my HttpClient. Just this:
httpClient.DefaultRequestHeaders.Authorization = request.Headers.Authorization;

How to serve multiple web sites by the one ADFS server?

I have two servers: one of them serves UI (it is called webUI) and another works with data (it is called webAPI).
I try to implement an authentication across the ADFS server. It has Relying Party Trusts for both servers: [urn=webui,identifier=address/webui],[urn=webapi,identifier=address/webapi].
I adjused the HttpConfiguration for webUI and user can be authenticated and use website, which the webUI serves (it's good).
var wsFedMetAdd = ConfigurationManager.AppSettings["wsFedMetAdd"];
if (string.IsNullOrWhiteSpace(wsFedMetAdd))
throw new ConfigurationErrorsException(Properties.Resources.InvalidMetadataAddress);
var wsFedWtrealm = ConfigurationManager.AppSettings["wsFedWtrealm"];
if (string.IsNullOrWhiteSpace(wsFedWtrealm))
throw new ConfigurationErrorsException(Properties.Resources.InvalidWtrealm);
appBuilder.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
});
var options = new WsFederationAuthenticationOptions
{
MetadataAddress = wsFedMetAdd,
Wtrealm = wsFedWtrealm,
SignInAsAuthenticationType = "Federation"
};
appBuilder.UseWsFederationAuthentication(options);
config.Filters.Add(new AuthorizeAttribute() { Roles = "Admin" });
Once client gets RequestSecurityTokenResponse (SAML Token). Also responses from ADFS set cookies for further requests (MSISAuth, MSISAuthenticated and so on).
The webAPI has the same implemention of HttpConfiguration (only one difference - wsFedWtrealm is urn:webapi instead urn:webui). Then I try send a request to the webAPI from client and the ADFS Server asks to authenticate one more.
I can't understand what should I do to use the same credentials for webAPI which I entered for webUI. Or maybe I should use SAML Token?
UPDATE
Wow. It is worked without SAML token, just using cookies.
When the user tries to be authenticated for webUI, diverse cookies are set on client (.AspNet.Federation, MSISAuth, MSISAuthenticated...). Then I substitute the webUI link with the webAPI link in the address bar and then webAPI doesn't ask to enter login and password. Hence data is displayed in browser. Authentication is picked up for webUI and for webAPI too.
But now problem is I get the error when javascript tries to send a request to webAPI:
XMLHttpRequest cannot load
https://my_address/adfs/ls/?wtrealm=urn%3awebapi&wctx=_ No 'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'https://my_address:9001' is therefore not allowed
access.
What version of ADFS?
You are mixing two protocols - Web API generally uses OAuth.
Use OpenID Connect for the UI and then that will naturally flow into the WebAPI as per this : Securing a Web API with ADFS on WS2012 R2 Got Even Easier.
Or for a somewhat more convoluted approach - what protocol to use with ADFS when security webapi for non-browser clients
This post help me to solve my problem.
I added to code of index.html new element iframe. Attribute src is the link to my webAPI.

Categories