Blazor app accesing multiple downstream APIs - c#

What I'm trying to do is to access Graph as well as another downstream API from Blazor Wasm app, I've gave the client app registration permissions to both Graph and Server App registration.
I have not been able to make Blazor switch the authorization schema between Graph and my API.
When I try to call my downstream api, the resulting token in header is the one belonging to Graph which is the only one who works, obviously it returns a invalid signature error.
Any lead to guide me thru the right thing to do in this case would be greatly appreciated.

access Graph as well as another downstream API from Blazor Wasm app
I assume you'd finished the code to call graph api, and you may already used the graph SDK or generate access token manually then send http request with the token. So you must set the api scope for it. And this is what I want to say, different API used different scope, and we can only set one type of API in one time. That's say, when we want to call MS graph api, we need to set the scope like User.Read, but if you want to call some other custom API which is protected by Azure AD, then the api should look like api://xxxxx/User.Read. This is the tutorial about how to protect API with AAD.
This is what I did in my side, first, creating a blazor wsam project with the Microsoft identity platform, this will help to build the template.
Then I need to have an Azure AD app which I think you already had it, update the variables in `appsetting.json.
"AzureAd": {
"Authority": "https://login.microsoftonline.com/{tenant_id}",
"ClientId": "Azure_ad_app_id",
"ValidateAuthority": true
}
Then I have the sign in module and we are able to sign in with microsoft account. I need to add the graph SDK into my project. Following this sample to add UserProfile.razor, UserProfileBase.cs, GraphClientExtensions.cs and modify Program.cs
Now, I can call graph API to get user profile. But since the api scope is User.Read now, it can't be used for another API. I created a webapi project in the same solution and followed the tutorial above which makes the API required a correct bearer token in the request header and added CORS policy for it.
Then in UserProfile.razor page, I modify the code like below to call the web api it worked for me.
#page "/profile"
#using Microsoft.AspNetCore.Authorization
#using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
#attribute [Authorize]
#inherits UserProfileBase
#inject IAccessTokenProvider TokenProvider
<h3>User Profile</h3>
<button #onclick="call">
call api
</button>
<div>#result</div>
#{
<table class="table">
<thead>
<tr>
<th>Property</th>
<th>Value</th>
</tr>
</thead>
<tr>
<td> DisplayName </td>
<td> #_user.DisplayName </td>
</tr>
<tr>
<td> UserPrincipalName </td>
<td> #_user.UserPrincipalName </td>
</tr>
</table>
}
#code {
private string result = "no data now";
private async Task call()
{
var http = new HttpClient();
//http.BaseAddress = new Uri("https://localhost:7018/WeatherForecast");
var tokenResult = await TokenProvider.RequestAccessToken(
new AccessTokenRequestOptions
{
Scopes = new[] { "api://aad_client_id_that_exposed_api/Tiny.Read" }
});
if (tokenResult.TryGetToken(out var token))
{
http.DefaultRequestHeaders.Add("Authorization",
$"Bearer {token.Value}");
result = await http.GetStringAsync("https://localhost:7018/WeatherForecast");
}
}
}

Related

how to find the error in blazor wabassembly app(very difficult to find the error)

difficulty to find the error in webassembly(client side) blazor
I am calling server side(blazor server app) webapi in client side((blazor webassembly app))
first I create a blazor server app project and then use built in webapi framework for crud operation
when I calling the webapi in client side then very very difficulty to find the error
then I create a blazor webassembly project and then add this below razor page inside pages folder
DisplayEmployeeData.razor
#page "/DisplayEmployeeData"
#using CrudBlazorServerApp.Data
#using System.Net.Http
#inject HttpClient Http
<h3>DisplayEmployeeData</h3>
#if (empList == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class='table'>
<thead>
<tr>
<th>empid</th>
<th>username</th>
<th>empaddress</th>
<th>password</th>
<th>country</th>
</tr>
</thead>
<tbody>
#foreach (var emp in empList)
{
<tr>
<td>#emp.empid</td>
<td>#emp.username</td>
<td>#emp.empaddress</td>
<td>#emp.password</td>
<td>#emp.country</td>
</tr>
}
</tbody>
</table>
}
#code {
Emp[] empList;
protected override async Task OnInitializedAsync() =>
empList = await Http.GetJsonAsync<Emp[]>("api/emps/"); //**here I put the debugger but emplist give the null**
}
what type error?
my webapi path is wrong?
see my console log very very difficulty to find the error?
The error is about < not being the valid start of a Json response, which indeed it isn't.
You are getting back an HTML page (with error information, probably).
From the text I gather that you created 2 separate projects. That means "api/emps/" cannot be a valid route. Your API and Client are probably running on localhost:xxxx and localhost:yyyy .
When you have fixed that routing you will probably run into a CORS problem, configure it on your server.
Be aware that a basic setup for this is provided when you create a Blazor Webassembly app and check the 'Hosted' box.
Try
empList = await Http.GetJsonAsync<Emp[]>("https://localhost:44333/api/emps");
and in your server Startup.Configure()
app.UseCors(policy =>
policy.WithOrigins("https://localhost:44399") // client address
.AllowAnyMethod()
.WithHeaders(HeaderNames.ContentType));

Can you change the Authority, ClientId, and DefaultAccessTokenScopes in Msal Authentication after adding it to the service provider

I want to be able to call a different app registers from a single web app. I'm not sure how to go about it. At first I thought I might be able to add multiple MsalAuthentications to my service provider than call a specific one. I couldn't get that to work, so now I'm wondering if it is possible to change the options of the MsalAuthentication I add at the begining.
Here is how I am adding MSAL Authentication:
builder.Services.AddMsalAuthentication(options =>
{
var authentication = options.ProviderOptions.Authentication;
authentication.Authority = FakeADTenant.TenantAddress;
authentication.ClientId = FakeADTenant.AppId;
authentication.ValidateAuthority = true;
options.ProviderOptions.DefaultAccessTokenScopes.Add(FakeADTenant.TokenScope);
});
On the other end, when the log in button is clicked, I'm using this page to log in:
#page "/authentication/{action}"
#using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="#Action" />
#code{
[Parameter] public string Action { get; set; }
}
I'm thinking I could possible change the options in the code before RemoteAuthenticatorView gets called. I only want to change the authentication.Authority, authentication.ClientId, and options.ProviderOptions.DefaultAccessTokenScopes.
No, as any OAuth provider, you cannot change the clientId, The Authority is the OAuth provider url. Redirect URIs must match URis register for the client. And you can send any of client allowed scopes in the authorize request.

How to set consent cookie in Blazor Server

I have a Blazor 3.1 App with Identity where I want to implement a cookie consent banner.
In classic ASP .NET Core, there is a nice template for a cookie consent banner.
#using Microsoft.AspNetCore.Http.Features
#{
var consentFeature = Context.Features.Get<ITrackingConsentFeature>();
var showBanner = !consentFeature?.CanTrack ?? false;
var cookieString = consentFeature?.CreateConsentCookie();
}
#if (showBanner)
{
<div class="container">
<div id="cookieConsent" class="alert alert-info alert-dismissible fade show" role="alert">
Use this space to summarize your privacy and cookie use policy. <a class="alert-link" asp-area="" asp-controller="Home" asp-action="Privacy">Learn More</a>.
<button type="button" class="accept-policy close" data-dismiss="alert" aria-label="Close" data-cookie-string="#cookieString">
<span aria-hidden="true">Accept</span>
</button>
</div>
</div>
<script>
(function () {
var button = document.querySelector("#cookieConsent button[data-cookie-string]");
button.addEventListener("click", function (event) {
document.cookie = button.dataset.cookieString;
}, false);
})();
</script>
}
If you are placing this partial in the layout, you have a perfect working cookie consent banner. If configured appropriately, users cant even login to the Identity Framework until they consent.
Because Blazor does not know a HttpContext, this template is not working. Is there a boilerplate way or do I have to create this feature this by myself?
I just solved this in a Blazor 3.1 app yesterday! I opted to use JS Interop and it was super easy.
My web app has multiple front ends, including MVC, Razor Pages and Blazor, so you can open the main solution file and then check out the Blazor project:
https://github.com/shahedc/NetLearnerApp
Things to note in the Blazor project;
I implemented the partial view as a razor component
look for /_CookieConsentPartial.razor in the Shared folder
MainLayout.razor uses this razor component
The _Host.cshtml file includes my js interop file
Look for netLearnerJsInterop.js in wwwroot
the js Interop file contains the document.cookie usage
the razor component uses JSRuntime.InvokeVoidAsync to call the JS method to accept the GDPR consent message and store the cookie
To set cookies on the client, you still need Javascript Interop.
See this answer: How do I create a cookie client side using blazor
Alternatively, you could store the information you need in the Local Storage using this library, without having to write any Javascript:
https://github.com/Blazored/LocalStorage
You can use this GDPR Consent component from Nuget.
However it is only helping you to save work on UI side. User chosen Cookie settings is stored in Local storage which can be read out any time. But it does not restricts any Cookies or Tracking scripts automatically. That must be done by the app developer!
See it in action on demo page.
Microsoft has released this article explaining a relatively simple way to implement a GDPR compliant using the CookiePolicyOptions Class and CookiePolicyAppBuilderExtensions.UseCookiePolicy Method. There appears to be dynamic validation to determine if consent is required.
builder.Services.Configure<CookiePolicyOptions>(options => {
// This lambda determines whether user consent for non-essential
// cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None; });
Post: EU General Data Protection Regulation (GDPR) support in ASP.NET Core

Question about AccountController and _LoginPartial in sample project

I am currently familiarizing myself with both the MVC framework and Azure B2C logins / credentials. The goal is to implement some of the logic provided in the sample project to an existing project. Before getting into details about my troubles with my own project, I was not able to figure out the following:
The sample app provided (cf. https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi) contains a class AuthController.cs, which provides logic for Signing In / Out, etc. The methods in the AuthController appear to be called when using a partial view _LoginPartial, e.g.
#if (Request.IsAuthenticated)
else
{
<ul class="nav navbar-nav navbar-right">
<li>#Html.ActionLink("Sign up / Sign in", "SignUpSignIn", "Account", routeValues: null, htmlAttributes: new { id = "signUpSignInLink" })</li>
</ul>
}
The sign-in-method in the AccountController takes an argument string redirectUrl, e.g
public void SignUpSignIn(string redirectUrl)
{
redirectUrl = redirectUrl ?? "/";
// Use the default policy to process the sign up / sign in flow
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = redirectUrl });
return;
}
Now, the controller does not seem to be accessible when Debugging, thus I cannot figure out from where the parameter redirectUrl is passed. The reason behind this is that I would like to implement the SignUpSignIn method in an already existing controller class.
What is the relationship between the partial class and the controller and from where do I get this redirectUrl. I am sorry if my question seems trivial, but I am currently making a transition from classic ASP.NET to MVC.
You need to configure the main domain name where you're hosting your app as a reply URL in the AAD registration for your app and pass it as the redirect_uri when redirecting to AAD to allow the user to sign in.
AAD will only redirect the access token to URLs that are specified as Reply URLs in the app registration in AAD.
So in this case, redirectUrl has been set to "https://localhost:44316/". The controller gets the redirectUrl value from the web.config file.
It must match with the Reply URL configured in Azure AD.

How to add LinkedIn to SignInManager.GetExternalAuthenticationSchemes()

How do I make a custom authentication provider like LinkedIn appear in SignInManager.GetExternalAuthenticationSchemes() from where Login.cshtml picks up by default
Background Details:
I am trying to understand asp.net core identity framework. In that quest, I created a standard .net core project
I tried out the supported Google authentication alongside reading the documentation and it all worked fine for me.
I was able to make LinkedIn authentication work for me, but couldn't understand how to make certain pieces work. To add support for LinkedIn authentication, I made the following changes
Added the below lines In Startup.Configure method
app.UseOAuthAuthentication(new OAuthOptions() {
AuthenticationScheme = "LinkedIn",
ClientId = Configuration["Authentication:LinkedIn:ClientID"],
ClientSecret = Configuration["Authentication:LinkedIn:ClientSecret"],
CallbackPath = new PathString("/signin-linkedin"),
AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization",
TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken",
UserInformationEndpoint = "https://api.linkedin.com/v1/people/~:(id,formatted-name,email-address,picture-url)",
Scope = { "r_basicprofile", "r_emailaddress" },
});
Added the required ClientId and ClientSecret to the configuration
Added the following line to the Login.cshtml
<button type="submit" class="btn btn-default" name="provider" value="LinkedIn" title="Log in using your LinkedIn account">LinkedIn</button>
All this works fine. Now my question is:
For supported authentication providers, as soon as I call, say, app.UseGoogleAuthentication in Startup.Configure, my call to SignInManager.GetExternalAuthenticationSchemes() in Login.cshtml lists Google as a provider. What do I need to do, so the call to SignInManager.GetExternalAuthenticationSchemes() will also list LinkedIn as a provider
What do I need to do, so the call to SignInManager.GetExternalAuthenticationSchemes() will also list LinkedIn as a provider
This method only lists the authentication middleware that have been assigned a "display name".
To include Linked in the providers list, set OAuthOptions.DisplayName:
app.UseOAuthAuthentication(new OAuthOptions
{
DisplayName = "LinkedIn"
// ...
});

Categories

Resources