Blazor WASM standalone to ASP.NET Core 6 Web API with AAD B2C Authorization - asp.net-core-webapi

I am building a Blazor WASM standalone UI that will use an existing ASP.NET Core 6 Web API for its backend. I have both the Blazor app and the Web API authenticating successfully to AAD B2C individually, but I can't get the UI to successfully pass the token to the API and always get a 401 from the API.
I'm referencing an NSWAG generated API Client from the Blazor app.
Web API
appsettings.js:
Program.cs:
Blazor WASM Standalone
appsettings.js:
Program.cs:
Page.cs:
Service.cs:

Even I got the Http 500 Error Initially. If the controller class has RequiredScope mentioned, remove that attribute.
In client appsettings.json it has to be Authority and in Server appsettingsjson it has to be Instance.
My WeatherForecastController.cs
[RequiredScope(RequiredScopesConfigurationKey = "AzureAdB2C:Scopes")]
My appsettings.json from Client App
appsettings.json file will be available under wwwroot folder
{
"AzureAdB2C": {
"Authority": "https://DomainInitialName.b2clogin.com/DomainInitialName.onmicrosoft.com/B2C_1_Sign_In",
"ClientId": "ClientID from Client AppRegistration",
"ValidateAuthority": false
}
}
Client Program.cs file
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using MySOBlazorApp.Client;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddHttpClient("MySOBlazorApp.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("MySOBlazorApp.ServerAPI"));
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("https://DomainInitial.onmicrosoft.com/Client Id of Client App Registration from Azure Portal/User Flow from the tenant");
});
await builder.Build().RunAsync();
My appsettings.json from Server App
{
"AzureAdB2C": {
"Instance": "https://DomainInitial.b2clogin.com",
"ClientId": "ClientID from Server AppRegistration",
"Domain": "DomainInitial.onmicrosoft.com",
"SignUpSignInPolicyId": "UserFlow Name"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
OutPut
References taken from MSDoc 1 and 2

I ended up resolving my issues with the help of this example: https://github.com/bryanknox/BlazorApiAadB2c/tree/main/BlazorSample
When newing up the HttpClient, I needed to add a custom AuthorizationMessageHandler and then configure the authorizedUrls and scopes since the UI is talking to an existing API with a different Base URL via a generated NSWAG client. I did this via an extension class.
From within Program.cs, I call the extension class like so: builder.Services.AddApiClient(builder.Configuration);
I also was using the GUID in the .ConfigureHandler(scopes... param URL, but actually needed to use it as it was defined within the B2C portal, which was with the actual API AAD B2C App Registration name.

Related

Auth setup of B2C Web API accessing confidential client (multitenant) Web API

I have a multi-tenant Web API of tenant A. It has permissions exposed and accepted by a B2C Web API of tenant B. (The API App Services live in the same tenant, but their AD instances are separate due to the one being a B2C tenant).
I have the following code in my B2C Web API authenticating with tenant B to access the multi-tenant Web API of tenant A.
I'm using Microsoft.Identity.Web (v1.25.5) and .NET Core (6), and so I don't have to handle making unnecessary calls to get an access token, I'm using the IDownstreamWebApi helper classes (though I have tried without according to the documentation, but land up with the same error.)
My code:
appsettings.json
program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(options =>
{
builder.Configuration.Bind("AzureAdB2C", options);
},
options => {
builder.Configuration.Bind("AzureAdB2C", options);
})
.EnableTokenAcquisitionToCallDownstreamApi(options =>
{
builder.Configuration.Bind("AzureAdB2C", options);
})
.AddDownstreamWebApi("TenantAApi", options =>
{
builder.Configuration.Bind("TenantAApi", options);
})
.AddInMemoryTokenCaches();
Calling code:
var response = await _downstreamWebApi.CallWebApiForAppAsync(
"TenantAApi",
options =>
{
options.HttpMethod = httpMethod;
options.RelativePath = url;
}, content);
var responseContent = await response.Content.ReadAsStringAsync();
The error I receive:
MSAL.NetCore.4.48.0.0.MsalClientException:
ErrorCode: tenant_override_non_aad
Microsoft.Identity.Client.MsalClientException: WithTenantId can only be used when an AAD authority is specified at the application level.
at Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder`1.WithTenantId(String tenantId)
at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForAppAsync(String scope, String authenticationScheme, String tenant, TokenAcquisitionOptions tokenAcquisitionOptions)
at Microsoft.Identity.Web.DownstreamWebApi.CallWebApiForAppAsync(String serviceName, String authenticationScheme, Action`1 downstreamWebApiOptionsOverride, StringContent content)
What doesn't make sense is that I'm calling this from a B2C Web API, from what I can see in the existing AbstractAcquireTokenParameterBuilder code (see line 292), B2C authorities are not AAD specific, and even so, adding an Authority or AadAuthorityAudience to my AzureAdB2C config object has no effect.
Am I missing a configuration property somewhere?
It seems that this isn't possible according to the following wiki post -
https://github.com/AzureAD/microsoft-identity-web/wiki/b2c-limitations#azure-ad-b2c-protected-web-apis-cannot-call-downstream-apis
For now I'm going to try a different approach and get an access token with a ConfidentialClientApplication object, and if that doesn't work, create a separate app registration in the other tenant and authenticate with that instead.

Angular 12 with .NET and Windows Domain Authentication

So I've trying to setup Angular login with Windows Domain Authentication and the backend is hosted with .NET.
Every resource about that on the internet suggest adding interceptor on Angular side:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({ withCredentials: true });
return next.handle(req);
}
and registering it in app.module
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: CredentialsInterceptor, multi: true}
],
then a sample http request in a service
this.http.get(this.baseUrl + 'user')
But making a request
returns 500 with cors policy error:
No 'Access-Control-Allow-Origin' header is present on the requested
resource.
And what is strange, necessary cors headers have been already added on backend. Since I'm working on frontend only, is there anything that could be added on Angular side?
What I'm expecting is a Windows Domain login window showing after making an http request.
Any help will be appreciated

Need to redirect from Web appplication to Zoho Desk

I have Zoho desk trail account.
We want to redirect from our web application to Zoho Desk Add Ticket page by clicking button.
I have configure SAML SSO from below mention link:
https://help.zoho.com/portal/en/kb/desk/for-administrators/user-access-and-security/articles/setting-up-saml-single-signon-for-help-center
How can i achieve this without login or auto signup/signin into Zoho Desk using SAML SSO?
Web application in .net core.
If any one have done this using code then please reply.
I have found one solution for this.
Download "Component SAML" package which is paid package, but you will get 30 day trial version or Install NuGet package "ComponentSpace.saml2".
Creates a self-signed X.509 certificate.
Upload public key on Zoho Desk SAML Configuration and add private key in your project (under certificate folder).
For Dot net core application you need to add below settings in appsettings.json file.
"SAML": {
"$schema": "https://www.componentspace.com/schemas/saml-config-schema-v1.0.json",
"Configurations": [
{
"LocalIdentityProviderConfiguration": {
"LocalCertificates": [
{
"FileName": "certificates/idp.pfx",
"Password": "password"
}
]
},
"PartnerServiceProviderConfigurations": [
{
"Name": "{Entity Id from Zoho desk SAML Screen}",
"SignSamlResponse": true,
"AssertionConsumerServiceUrl": {Help Center SAML Response URL}
}
]
}
]
},
"ZohoDeskSettings": {
"PartnerName": "{Entity Id from Zoho desk SAML Screen}",
"RelayState": "{url on which you want to redirect after SSO}"
}
Add below code in Startup.cs (ConfigurationService method)
services.AddSaml(Configuration.GetSection("SAML"));
Add below code in your controller from where you will click link to redirect to zoho portal
[Authorize]
[HttpGet]
public async Task<IActionResult> InitiateSingleSignOn()
{
// Get the name of the logged in user.
var userName = User.Identity.Name;
// For demonstration purposes, include some claims.
var attributes = new List<SamlAttribute>()
{
new SamlAttribute(ClaimTypes.Email, /*{Users Email}*/),
new SamlAttribute(ClaimTypes.GivenName, /*{Users FirstName}*/),
new SamlAttribute(ClaimTypes.Surname, /*{Users LastName}*/),
};
var partnerName = /*{Get setting from appsettings.json}*/;
var relayState = /*{Get setting from appsettings.json}*/;
// Initiate single sign-on to the service provider (IdP-initiated SSO)
// by sending a SAML response containing a SAML assertion to the SP.
// The optional relay state normally specifies the target URL once SSO completes.
await _samlIdentityProvider.InitiateSsoAsync(partnerName, userName, attributes, relayState);
return new EmptyResult();
}
You can also check example provided by Component SAML

Protect ASP.NET API with IdentityServer & Bearer

I have a 3 tier application, where I have:
A blazor web application
a HTTP API
An Identity Server
Currently, I have 2 kinds of controllers on the HTTP API:
general-purpose API: can be accessed without authentication, but only if the call comes from the Web Application
personal purpose API: can be accessed only after authentication
Right now, the "personal purpose API" working fine. this API is only accessible when the user is logged in.
But, I also need to protect the "general-purpose API" from any hacker, right now a call to "post/list" returns an "unauthorized error"!
I wish to protect this API, without authentication, but it must be only accessible from my web application.
Do you know how can I do this? Is there something wrong or missing in my code?
Here is the controller code on the HTTP API side :
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class PostsController : ControllerBase
{
[HttpGet]
[Route("post/list")]
public async Task<List<Item>> GetListAsync()
{
...
}
}
Here is my code on the HTTP API side :
context.Services.AddAuthentication(Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = true;
options.ApiName = "SoCloze";
});
And here is my code on the Web Application side:
context.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(ApplicationConstants.LoginCookieExpirationDelay);
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = true;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.ClientId = configuration["AuthServer:ClientId"];
options.ClientSecret = configuration["AuthServer:ClientSecret"];
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("role");
options.Scope.Add("email");
options.Scope.Add("phone");
options.Scope.Add("SoCloze");
options.ClaimActions.MapAbpClaimTypes();
});
You should make your controller anonymous. With your configuration simply remove your Authorize attribute from your controller. If you had applied a global policy you'd need to add the anonymous attribute to your controller:
[AllowAnonymous]
I'm not sure what you want to achieve, but I guess you want cross-site protection. That is, you want to limit certain endpoints from being accessed only from the origin of your blazor app.
You need to trust in the client browser for Same Origin Policy. The same origin policy controls interactions between two different origins. Cross-origin writes are typically allowed, Cross-origin embedding is typically allowed and Cross-origin reads are typically disallowed, but read access is often leaked by embedding.
This protection is only for XHR calls from your browser and it doesn't protect againts direct access (postman, fiddler, any http client...).
To prevent cross-origin writes in your posts you can use Antiforgery tokens. Asp Net Core create this tokens automatically if you specify POST in your forms:
<form asp-controller="Todo" asp-action="Create" method="post">
...
</form>
Or
#using (Html.BeginForm("Create", "Todo"))
{
...
}
This will reject any post from any form that has not been previously loaded from your server.
To keep the API simple, you of course want to only accept tokens from the trusted IdentityServer.
So basically you need a generic access token that is secure, but not tied to any human user. You could have the web app to request an access token using the client credentials flow and then use that token all the time when you want to access the API as a non-authenticated user.
In IdentityServer you would setup a separate client just for this.
Also using the IdentityModel library could help you out here (as a Worker Applications) https://identitymodel.readthedocs.io/en/latest/aspnetcore/worker.html
see also https://docs.duendesoftware.com/identityserver/v5/tokens/requesting/
This picture shows what I mean:

Using asp.net 4.5 OAuth to register google with clientid and secret

I notice in the asp.net 4.5 template, all the authorization samples besides google pass in secret and clientid. How can I pass in my google secret and clientid? Brock has a good discussion here that I'm following:
http://info.develop.com/blogs/bid/232864/ASP-NET-Using-OAuthWebSecurity-without-SimpleMembership#.UNuBh2_Adv9
The sample code is as follows that comes with the template.
internal static class AuthConfig
{
public static void RegisterOpenAuth()
{
// See http://go.microsoft.com/fwlink/?LinkId=252803 for details on setting up this ASP.NET
// application to support logging in via external services.
//OpenAuth.AuthenticationClients.AddTwitter(
// consumerKey: "your Twitter consumer key",
// consumerSecret: "your Twitter consumer secret");
//OpenAuth.AuthenticationClients.AddFacebook(
// appId: "your Facebook app id",
// appSecret: "your Facebook app secret");
//OpenAuth.AuthenticationClients.AddMicrosoft(
// clientId: "your Microsoft account client id",
// clientSecret: "your Microsoft account client secret");
// OpenAuth.AuthenticationClients.AddGoogle();
}
}
I had a look at the source code of the OAuthWebSecurity class.
The reason Google's authentication client doesn't need an AppId/AppSecret is because its implementation is using OpenId and NOT OAuth.
If you want to use OAuth with Google you'll have to write your own client (at least for right now).

Resources