How to secure an Azure Function accessed by a Blazor Wasm app with Azure AD B2C? - azure

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");
}
}

Related

azure oauth 2.0 how to protect web api from external organization web api?

I am new to Azure and trying to protect/web api hosted in azure using oauth 2.0.
This web api will be called from other web api/deamon which is in control of other organization.
I am aware of client credential flow, but in this scenario external api is hosted outside azure ad. We have no idea of where it is hosted and how this third external web api/deamon is hosted? How should we do authentication/authorization for our web api, so that any external service can use it?
You know about client credential flow, then you should know that this kind of flow doesn't need a user to sign in to generate access token, but only need an azure ad application with the client secret. This azure ad application can come from your tenant, so it doesn't require the web api/deamon which is in control of other organization to have an application, you can create the app in your tenant then provide it to the external web api. What you need to make sure is that the external is really a daemon application.
Let's assume that the external app that need to call your api which is protected by azure ad is a daemon application, then client credential flow is suitable here.
Code for external api to generate access token
//you can see it when you add api permission
var scopes = new[] { "api://exposed_apis_app_id/.default" };
var tenantId = "your_tenant_name.onmicrosoft.com";
var clientId = "YOUR_CLIENT_ID";
var clientSecret = "YOUR_CLIENT_SECRET";
// using Azure.Identity;
var options = new TokenCredentialOptions{AuthorityHost = AzureAuthorityHosts.AzurePublicCloud};
var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret, options);
var tokenRequestContext = new TokenRequestContext(scopes);
var token = clientSecretCredential.GetTokenAsync(tokenRequestContext).Result.Token;
Code for your api to add authentication for azure ad, you still have some more configurations, you can refer to my this answer, some related document: authorize the token with role and jwt token configuration.
[Authorize]
public class HelloController : Controller
{
public IActionResult Index()
{
HttpContext.ValidateAppRole("User.Read");//You set it when adding app role
Student stu = new Student();
stu.age = 18;
return Json(stu) ;
}
}
appsettings:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "2c0xxxxxxx57",
"Domain": "tenantname.onmicrosoft.com", // for instance contoso.onmicrosoft.com. Not used in the ASP.NET core template
"TenantId": "common",
"Audience": "8fxxxx78"
}
startup.cs, don't forget "app.UseAuthentication();app.UseAuthorization();" in Configure method
public void ConfigureServices(IServiceCollection services)
{
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
services.AddControllersWithViews();
}
create an azure ad application in your tenant and expose an api with a role.
you can create another azure ad application, add client secret and add the application permission created before in the API permissions blade.
provide the application id and client secret to those external app and let them use these to generate access token, then they can use the access token to call your api.
modify your api to authorize the token if has the correct role.

OAUTH / Azure Functions: Method to auth AAD user for endpoints that don't support service principals

I've been leveraging Azure Function Apps to automate items in Azure. I currently have working functions that connect to Microsoft Graph, Resource Explorer, KV etc. using service principal / OAUTH client credentials flow (inside the function app). To call my function app, I've implemented implicit flow. While I'm not an expert at OAUTH, I am familiar enough now to get this configured and working.
However, there are Azure endpoints I need to use that don't support using a service principal token, they only support an actual AAD user requesting a token. Here's one that I want to run: Create synchronizationJob
If you look at the permissions section of the above link, you'll see that "application" is not supported. I did test this in a function: I can run these endpoints in Graph Explorer fine (as myself), but they fail in the function when using a token linked to a service principal.
Since this new automation is going to be an Azure Function (and not an interactive user), I can't use the authorization code flow. I need this service account's OAUTH to be non-interactive.
TL;DR
I can run the above endpoint in Azure's Graph Explorer just fine:
Azure Graph Explorer
since I'm authenticating as myself, and have a token generated based on my user ID. But for automating using Azure Functions where I need to use this endpoint (which doesn't support OAUTH using an SP), I need some way to have a back-end AAD user auth and pull a token that can be used to run the endpoint.
Any help is welcome! Feel free to tell me that I'm either missing something very basic, or not understanding a core principal here.
As juunas mentioned no guarantee that will work though, I test in my side and it seems doesn't work although I assigned "Global administrator" role to the service principal.
For your situation, you can request the access token in your function code and then use the access token to request the graph api.
Add the code like below in your function to get access token.
HttpClient client = new HttpClient();
var values = new Dictionary<string, string>
{
{ "client_id", "<your app client id>" },
{ "scope", "<scope>" },
{ "username", "<your user name>" },
{ "password", "<your password>" },
{ "grant_type", "password" },
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("https://login.microsoftonline.com/<your tenant id>/oauth2/v2.0/token", content);
var responseString = await response.Content.ReadAsStringAsync();
var obj = JObject.Parse(responseString);
var accessToken = (string)obj["access_token"];
And then use the access token got above to request graph api.

Blazor standalone client calling a function via Azure B2C

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 …
}

Azure active directory v2.0 query for Web API integration on a SharePoint site

We have a SharePoint publishing site with anonymous access hosted on the internet. As per out latest requirements, we need to implement user login (AzureAD, Microsoft personal and work accounts, and more).
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-flows
As per the documentation here, we want to implement this using Web API to get the secure information from the database. We are thinking about using MSAL.js file for user login and logout on the SharePoint and after getting a bearer token we can call the Web API for the additional data from our database.
Standalone Web APIs restriction: “You can use the v2.0 endpoint to build a Web API that is secured with OAuth 2.0. However, that Web API can receive tokens only from an application that has the same Application ID. You cannot access a Web API from a client that has a different Application ID. The client won't be able to request or obtain permissions to your Web API.”
How can we create two applications with same application ID at App Registration Portal? Or should we use the same application ID at SharePoint and Web API’s end?
There is no need to register two application, you only need to one register application. After you register the application, you can using the MSAL library below to get the token to call the web API:
<script class="pre">
var userAgentApplication = new Msal.UserAgentApplication("e5e5f2d3-4f6a-461d-b515-efd11d50c338", null, function (errorDes, token, error, tokenType) {
// this callback is called after loginRedirect OR acquireTokenRedirect (not used for loginPopup/aquireTokenPopup)
})
userAgentApplication.loginPopup(["user.read"]).then(function (token) {
var user = userAgentApplication.getUser();
console.log(token);
// signin successful
}, function (error) {
// handle error
});
</script>
And to protect the web API, you can use the same app and refer the code below:
public void ConfigureAuth(IAppBuilder app)
{
var tvps = new TokenValidationParameters
{
// The web app and the service are sharing the same clientId
ValidAudience = "e5e5f2d3-4f6a-461d-b515-efd11d50c338",
ValidateIssuer = false,
};
// NOTE: The usual WindowsAzureActiveDirectoryBearerAuthenticaitonMiddleware uses a
// metadata endpoint which is not supported by the v2.0 endpoint. Instead, this
// OpenIdConenctCachingSecurityTokenProvider can be used to fetch & use the OpenIdConnect
// metadata document.
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration")),
});
}

Azure AD OpenIDConnect + ASP.NET Core - Authenticate and Extra Permissions/Token?

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.

Resources