I have an MVC Core 2.1 application, and am using Azure AD B2C (2.1.1) as the identity provider.
I want to call a function when a user signs in and signs out, and am guessing I need to put this in the middleware.
In the previous version of the AD B2C, there was a function:
app.UseOpenIdConnectAuthentication(options);
which had options that made notification functions available:
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = this.AuthenticationFailed,
RedirectToIdentityProvider = this.OnRedirectToIdentityProvider
}
The app.UseOpenIdConnectAuthentication(options); function is now marked as obsolete.
I could put in a middleware call like this:
app.Use((context, next) =>
{
if(/*context handler referrer includes "B2C_1_SignUpPolicy"*/)
{
// User has signed-in
}
return next.Invoke();
}
… but this would be called for every request and cannot be adapted to also call a function when the user signs out.
So do I set up notifications in the app.UseAuthentication(); version?
I've created a code sample at GitHub for an ASP.NET Core 2.1 web application that authenticates an end user against Azure AD B2C using the ASP.NET Core 2.1 authentication middleware for Azure AD B2C, acquires an access token using MSAL.NET, and accesses a web API using this access token.
It acquires the access token by handling the OpenIdConnectOptions.Events.OnAuthorizationCodeReceived event.
You might be able to develop the similar code to handle another OpenIdConnectOptions.Events event.
Related
Scenario: I have a Blazor wasm app secured with B2C Authentication that needs to call an HTTP triggered Azure function. What would be the best method to secure that Azure function so that only the Blazor app and/or authenticated users could call that function?
So far I know how to secure the Blazor app with B2C (obviously silly!) and I've also been able to add B2C auth to an Azure function and secure the calls by validating the jwt token. But it's not clear in my head how the two parts should jive together.
Should I expose an API in the app registration of the Azure Function in the B2C tenant? If so, how the Blazor app would make authenticated calls to the Azure function?
Or do I simply send the jwt token from the Blazor app through the http request headers of the Azure function call and then validate that token manually inside the function?
I've been reading a lot of different posts on the subject lately but I still can't figure out what's THE best solution to achieve it.
Any help/cues would be appreciated.
Thanks!
ps: I'm not interested in using the Azure API management since it's a little bit on the pricey side for a pretty simple app solution.
If you want to call Azure function projected by Azure AD B2C, please refer to the following steps
Configure Azure AD B2C for Azure function
Create Azure B2C app.
Web App/API : Yes
Allow Implicit Flow : Yes
Set Reply URL in B2C app: https://{function app url}/.auth/login/aad/callback
Set App ID URL in B2C App : https://{tennat}/{prefix}
Note down B2C apps Application ID.
Define API scope. Go to B2C app => Published scopes.
Get your B2C user flows/policy’s metadata URL. Note down this URL.
It can be obtained from Run User Flow page.
It’s format is like https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/v2.0/.well-known/openid-configuration?p={policy}.
Go to your functions => Authentication / Authorization.
Set following
App Service Authentication : On
Action to take when not authenticated : Login with Azure AD
Authentication providers : Azure AAD
Management Mode : Advanced
Client Id : {Application Id from Step 4}
Issuer URL : {URL from step 6}
Allowed Audience: {Application Id URL from Step 3}
Create Client application For more details, please refer to here.
Configure CORS policy in Azure Function
Configure Application
Create
dotnet new blazorwasm -au IndividualB2C --aad-b2c-instance "{AAD B2C INSTANCE}" --client-id "{CLIENT ID}" --domain "{TENANT DOMAIN}" -o {APP NAME} -ssp "{SIGN UP OR SIGN IN POLICY}"
Code
Create Custom AuthorizationMessageHandler class
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "<your function app url>" },
scopes: new[] { "<the function app API scope your define>" });
}
}
Add the following code in Program.cs.
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
// register CustomAuthorizationMessageHandler
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
// configure httpclient
// call the following code please add packageMicrosoft.Extensions.Http 3.1.0
builder.Services.AddHttpClient("ServerAPI", client =>
client.BaseAddress = new Uri("<your function app url>"))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
// register the httpclient
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("ServerAPI"));
// configure Azure AD auth
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("<the function app API scope your define>");
});
await builder.Build().RunAsync();
}
Call the API
#page "/fetchdata"
#using Microsoft.AspNetCore.Components.WebAssembly.Authentication
#inject HttpClient Http
<h1>Call Azure Function</h1>
<p>This component demonstrates fetching data from the server.</p>
<p>Result: #forecasts</p>
<button class="btn btn-primary" #onclick="CallFun">Call Function</button>
#code {
private string forecasts;
protected async Task CallFun()
{
forecasts = await Http.GetStringAsync("api/http");
}
}
Is there a good example or a walkthrough of a Blazor standalone app calling a function app via an Azure Active Directory B2C passing in a claim with an identity to the function?
I have been working my way through the documentation,
Secure an ASP.NET Core Blazor WebAssembly standalone app with Azure Active Directory B2C and
ASP.NET Core Blazor WebAssembly additional security scenarios but cannot get past 404 result.
So what am I trying to achieve? I want to put together a couple of POCs. The first is a Blazor standalone client app, authenticating via B2C, then passing an authorisation token claims token to an azure function, where I can unpack the user id and email address. The second is same as above, but with Api Management between the Blazor client and the functions api. Because of the nature of the project I am using the consumption plan for both the functions and api management.
So just concentrating on the first case (Blazor - B2C - Function), on the assumption if I get that to work, the api will follow…
I have a B2C client tenant with 2 applications: a front end app authorised for the scopes of a 2nd B2C application for the api. My Function app has authentication/authorisation set to 'Login with Active Directory' with the client ID set to the Front end app's client id, the issuer uri set to the B2C's pocsignupsignin Userflow and the 'Allowed Token Audiences' set to the client id of the Api in B2C.
I can get a JWT token via a browser and using postman successfully call a function in my function app passing a bearer token.
My Blazor app can log in to B2C. If I have no authorisation configured for the web app, then my function call is successful.
But once I turn authorisation I run into CORS 404 - origin 'https://localhost:44389' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. But I have CORS configured with 'Enable Access-Control-Allow-Credentials' and my client url configured.
What am I missing?
I also suspect that the token with the id will not be passed correctly.
From Program.cs
builder.Services.AddHttpClient("ServerAPI",
client => client.BaseAddress = new Uri(functionURI))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add(readScopeURI);
options.ProviderOptions.DefaultAccessTokenScopes.Add(writeScopeURI);
Console.WriteLine($"options.ProviderOptions.DefaultAccessTokenScopes {options.ProviderOptions.DefaultAccessTokenScopes}");
});
From CustomAuthorizationMessageHandler
public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { B2CClientUrl },
scopes: new[] { "read", "write" });
}
}
From appsettings
"AzureAdB2C":
{
"Authority": B2C signupsignin uri,
"ClientId": B2C frontend client,
"ValidateAuthority": false
}
Function call
async Task GetFromDedicatedFunctionClient(string subUrl)
{
try
{
var client = ClientFactory.CreateClient("ServerAPI");
Console.WriteLine($"client.BaseAddress {client.BaseAddress}");
result =
await client.GetStringAsync(subUrl);
}
catch …
}
I have a service that gets an access token from Azure AD. I have an API that I would like to accept that token as authorization.
My service code to call the API is
HttpClient client = new HttpClient()
{
BaseAddress = new Uri("https://localhost:44372/")
};
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, client.BaseAddress + "api/todolist");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = await client.SendAsync(request);
The response I get back is a 401 - Unauthorized.
I have a feeling that the issue is in the API ConfigureServices function; specifically (this was taken from an example, so I don't really know what it means yet):
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
options.TokenValidationParameters.RoleClaimType = "roles";
});
I'm new to Azure and authentication in general so I don't know what options are available or appropriate. I also am not sure how to set up the applications in Azure to allow this. I have the application id of the service set up as an Authorized client application of the API; it is also listed int the knownClientApplications in the API manifest.
There are just so many knobs to turn, I have no idea where to go from here. If anyone can let me know some things to try, that would be outstanding.
Thanks
Here is a code sample on how to call a web API in an ASP.NET Core web app using Azure AD:
https://learn.microsoft.com/en-us/samples/azure-samples/active-directory-dotnet-webapp-webapi-openidconnect-aspnetcore/calling-a-web-api-in-an-aspnet-core-web-application-using-azure-ad/
This sample contains a web API running on ASP.NET Core 2.0 protected by Azure AD. The web API is accessed by an ASP.NET Core 2.0 web application on behalf of the signed-in user. The ASP.NET Web application uses the OpenID Connect middleware and the Active Directory Authentication Library (ADAL.NET) to obtain a JWT bearer token for the signed-in user using the OAuth 2.0 protocol. The bearer token is passed to the web API, which validates the token and authorizes the user using the JWT bearer authentication middleware.
My SignalR Hub requires users to be authenticated and the following line gives me user's Id:
var userId = Context.UserIdentifier;
Similarly, I can get the whole set of claims with:
var user = Context.User.Claims;
This works fine if I use SignalR locally but if I switch to Azure SignalR Service, I get a null value in Context.UserIdentifier. I also get no claims.
Here's what I'm changing in my Startup.cs:
In the ConfigureServices() method, I use
services.AddSignalR().AddAzureSignalR(Configuration["AzureSignalR:ConnectionString"]);
instead of
services.AddSignalR();
And in the Configure() method, I use:
app.UseAzureSignalR(routes =>
{
routes.MapHub<Hubs.MyHub>("/chat");
});
instead of:
app.UseSignalR(routes =>
{
routes.MapHub<Hubs.MyHub>("/chat");
});
Do I need anything else in Azure SignalR configuration to make sure I get user's claims? Any idea why this one change prevents claims from coming through?
P.S. I'm using Azure AD B2C for user authentication and as I said, if I use SignalR locally, everything works which means the code that handles grabbing JWT token from QueryString is working fine.
SignalR service will automatically inherit the claims from your authenticated user, no special configuration is needed. I just tried Azure AD B2C sample with SignalR service and the claims can be get from HubCallerContext.
Could you please check the SignalR access token returned from negotiation to see whether the claims are returned from server at the first place? (Decode it from base64 then you'll see the claims)
I am using the following bits against my Azure AD to authenticate with ASP.NET Core.
https://azure.microsoft.com/en-us/resources/samples/active-directory-dotnet-webapp-openidconnect-aspnetcore/
https://github.com/Azure-Samples/active-directory-dotnet-webapp-openidconnect-aspnetcore
I have the basic login/auth working after creating an Azure AD app. User can login/logout.
My question is given this, what's the best way when a user Auth's to log to a DB? I thought about making the redirect URL to an endpoint, saving, then just redirecting back to "Home" but is that ideal?
Also, is it possible to retrieve a bearer token via this approach? Or does this require another type of call or extending "scope"? So that for example I could retrieve the authenticated users Manager.
https://graph.microsoft.com/v1.0/me/manager
My question is given this, what's the best way when a user Auth's to log to a DB? I thought about making the redirect URL to an endpoint, saving, then just redirecting back to "Home" but is that ideal?
This way only able to log those who already sign-in your app successfully. It is not able to log those users who are attempt to sign-in your app but enter the wrong password.
Azure AD already provide lots of report to gain visibility into the integrity and security of your organization’s directory.( refer here)
And if you are using the Azure AD Premium, you can review the sign-in activities via the Azure new portal below:
And if you want to store the sign-in activity in your web app, you can write the custom code after the token is verified. Here is the code for your reference:
// Configure the OWIN pipeline to use OpenID Connect auth.
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = Configuration["AzureAD:ClientId"],
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAd:Tenant"]),
ResponseType = OpenIdConnectResponseType.IdToken,
PostLogoutRedirectUri = Configuration["AzureAd:PostLogoutRedirectUri"],
Events = new OpenIdConnectEvents
{
OnRemoteFailure = OnAuthenticationFailed,
OnTokenValidated = context => {
//write the custom code to store users login-in
return Task.FromResult(0); }
},
});
Also, is it possible to retrieve a bearer token via this approach?
Yes. We can get the token after receive the authorization code. You can refer the code sample here to acquire the token from asp.net core app.