Facing issues in consuming an Azure Active Directory enabled Azure Functions in a azure web application - azure-active-directory

I have enabled AAD Authentication for an Azure Function and then tried to consume the Function App (HTTP Trigger) in a web application but getting Unauthorized issue.
I also tried consuming it by creating a function proxy but the issue still persists.
Process Followed:
Created two AD Application (Web App, Azure Functions) and gave the
permission of Azure Functions AD to the Web App AD Created a basic
http trigger function
Enabled Authentication for Azure Functions by providing the details of Azure
Functions
Created a web application and during the access token generation, provided
the Client ID,Secret of web application and Audience URI( App ID) of Azure F
Unctions AD.
ClientCredential clientCredential = new ClientCredential(ConfigurationManager.AppSettings["ida:ClientId"], ConfigurationManager.AppSettings["ida:SecretKey"]);
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority);
AuthenticationResult result = await authContext.AcquireTokenAsync(ConfigurationManager.AppSettings["azrfunc:ResourceID"], clientCredential);
string requestUrl = "https://xxxx.azurewebsites.net/api/HttpTriggerCSharp1?code=Gxxxxx==&name=xxxx";
// Make the GET request
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = client.SendAsync(request).Result;

According to your description, I assumed that you are using Authentication and authorization in Azure App Service for your azure function app.
And as How authentication works in App Service states as follows:
Users who interact with your application through a web browser will have a cookie set so that they can remain authenticated as they browse your application. For other client types, such as mobile, a JSON web token (JWT), which should be presented in the X-ZUMO-AUTH header, will be issued to the client. The Mobile Apps client SDKs will handle this for you. Alternatively, an Azure Active Directory identity token or access token may be directly included in the Authorization header as a bearer token.
Based on your scenario, I created my two aad apps and set the required permission for my web app to access the aad app of my function app as follows:
And enable AAD authentication for my azure function app as follows:
Then getting the access token by using the following code:
var clientCredential = new ClientCredential("{clientId-for-my-web-app}", "{clientSecret-for-my-web-app}");
var authContext = new AuthenticationContext("https://login.windows.net/{tenantId}");
var result = await authContext.AcquireTokenAsync("{clientId-for-my-function-app}", clientCredential);
TEST:
In summary, you could decode your access token by using https://jwt.io/ and check the aud as follows:
Moreover, I noticed that your requestUrl contains the query string code. If you both enable the function level authorization and the user-based authentication, you also need to make sure your function key or master key is correct. Also, you could just set the anonymous authorization level for your azure function and just leverage the user-based authentication.

Related

ASP.NET 6 WebAPI Authentication with SSO

I have an ASP.NET 6.0 Web API project. I would like to add authentication and authorization to it, but it must use SSO via Azure.
We already have a SPA application that does this, it uses the Angular MSAL library to redirect the user to an SSO Login page, then returns to the SPA with an access token. The access token is then added to the header of each request to the Web API, which uses it to enforce authentication.
Now we want to share our web API with other teams within our organization, and we would like to have that login process just be another API call, rather than a web page.
Conceptually, a client would hit the /login endpoint of our API, passing in a userID and password. The web API would then get an access token from Azure, then return it as the payload of the login request. It's then up to the client to add that token to subsequent request headers.
I have done this with regular ASP.NET Identity, where all of the user and role data is stored in a SQL database, but since our organization uses SSO via Azure Active Directory, we would rather use that.
I have researched this topic online, and so far all of the examples I have seen use a separate SPA, just like we already have. But as this is a web api, not a front-end, we need to have an API method that does this instead.
Is this even possible? I know Microsoft would rather not have user credentials flow through our own web server, where a dishonest programmer might store them for later misuse. I understand that. But I'm not sure there's a way around this.
Thanks.
I believe you are looking for the Resource Owner Password (ROP) flow. You can use IdentityModel.OidcClient to implement it.
Sample code:
public class Program
{
static async Task Main()
{
// call this in your /login endpoint and return the access token to the client
var response = await RequestTokenAsync("bob", "bob");
if (!response.IsError)
{
var accessToken = response.AccessToken;
Console.WriteLine(accessToken);
}
}
static async Task<TokenResponse> RequestTokenAsync(string userName, string password)
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Constants.Authority);
if (disco.IsError) throw new Exception(disco.Error);
var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "roclient",
ClientSecret = "secret",
UserName = userName,
Password = password,
Scope = "resource1.scope1 resource2.scope1",
Parameters =
{
{ "acr_values", "tenant:custom_account_store1 foo bar quux" }
}
});
if (response.IsError) throw new Exception(response.Error);
return response;
}
}
Sample taken from IdentityServer4 repository where you can find more ROP flow client examples.
I would recommend that you don't go with this implementation and instead have all clients obtain their access tokens directly from Azure AD like you did with your Angular SPA.

EasyAuth with a SPA and AzureFunction on different hosts

I'm trying to use EasyAuth (aad) with a SPA, which is on "localhost:8080" at the moment, and an Azure Function which is hosted in Azure ({function-app}.azurewebsites.net. The intent is for the SPA to call a secured endpoint on the Azure Function. So, I have the Azure Function Registered as an application in AD, and the authentication redirect in the SPA to the Azure Function EasyAuth endpoint appears to be working, but the redirect back to the localhost SPA via the post_login_redirect_url is not.
I added http://localhost:8080 to the AAD registered application as an allowed redirect URI. However, if I fully qualify the URL I am redirected back to {function-host}/.auth/login/done. Is there an expectation that the SPA runs under the same hostname as the azure function, or is there a way to configure the setup to allow any URL for the SPA host?
Behavior
In terms of HTTP data during behavior, once login succeeds .auth/login/aad/callback is loaded with the following prior to redirecting to the default done page and stopping.
Response Header
Location = {function-host}/.auth/login/done
Form Data:
state = http://localhost:8080
code = auth code
id_token = auth token
How I called it from the SPA
function processAuthCheck(xmlhttp) {
if (xmlhttp.status == 401) {
url = "https://{function-app}.azurewebsites.net/.auth/login/aad?"
+ "post_login_redirect_url=" + encodeURI("http://localhost:8080");
window.location = url;
} else if (xmlhttp.status != 200) {
alert("There is an error with this service!");
return;
}
var result = JSON.parse(xmlhttp.responseText);
console.log(JSON.stringify(result));
return;
};
Regarding the issue, please refer to the following steps
Register Azure AD application to protect azure function with easy auth
Register client-side application
a. Register single-page application
b. In the Implicit grant and hybrid flows section, select ID tokens and Access tokens.
c. Configure API permissions
Enable CORS in Azure function
Code
a. Integrate Azure AD auth in your spa application with Implicit grant flow. After doing that, when users access your application, they need to enter their AD account to get access token
b. Client exchanges this accessToken for an 'App Service Token'. It does this by making a POST to https://{app}.azurewebsites.net/.auth/login/aad with the content { "access_token" : "{token from Azure AD}" }. This will return back an authenticationToken
c. Use that authenticationToken in a header named x-zumo-auth. Make all requests to your function app using that header.
For more details, please refer to here and here. Regarding how to implement Azure AD in spa, please refer to here.

How to obtain an Azure B2C bearer token for a non-interactive/daemon application and get it validated in an Azure HTTP-triggered function

There is a C# application under development that is supposed to be a part of a bigger backend application to process some data. This application is supposed to obtain a token from Azure AD B2C and send it to an HTTP-triggered function where it is supposed to be validated by the following code:
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(
$"{_authenticationSettings.Authority}/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever());
var config = await configManager.GetConfigurationAsync();
_validationParameters = new TokenValidationParameters
{
IssuerSigningKeys = config.SigningKeys,
ValidateAudience = true,
// Audience MUST be the app ID aka clientId
ValidAudience = _authenticationSettings.ClientId,
ValidateIssuer = true,
ValidIssuer = config.Issuer,
ValidateLifetime = true
};
var tokenHandler = new JwtSecurityTokenHandler();
var result = tokenHandler.ValidateToken(authHeader.Parameter, _validationParameters, out var jwtToken);
First, we thought that obtaining an access token from Microsoft Graph API using MSAL would help us but the C# code above threw an invalid signature exception which we discovered makes sense due to this GitHub post. Apparently, we need to obtain an id_token instead in the application and send it to the HTTP-triggered function for validation by the code snippet above.
The application cannot obtain the id_token because it's not supposed to launch Azure AD B2C's login UI to have a user sign-in and redirect it through a URL. What is the solution to this problem so that the application would obtain a token without a UI and send that to the http-triggered function for validation?
Obtaining a token for the AAD B2C tenant without UI is possible in two ways and you should probably pick one depending on what exactly you want to achieve:
user token - by using Resource Owner Password Credentials flow - https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-ropc-policy. This flow is deprecated though and mentioned usually in legacy application context
server-side application token - by using Client Cretendial flow - this on the other hand requires using requests specific for AAD but with AAD B2C tenant - https://learn.microsoft.com/en-us/azure/active-directory-b2c/application-types#daemonsserver-side-applications
I'm also not quite sure why should you use id_token for that. If the application needs to authorize the request to the function with the token then it should be an access token regardless of how the token is retrieved (interactive UI or not).

Blazor WASM calling Azure AAD secured Functions API

I have an Azure Functions API which uses Azure Active Directory authentication. I can test locally and deployed using a browser and curl calls in a process of:
Get a code
Use the code to get a token
Pass the token to authenticate and get the function result.
I now want to call this API from my Blazor WASM app but I'm sure there must be a nice MSAL call to do all the authentication but I cannot find any documentation on what that might be.
Does anyone have a code snippet to illustrate what needs to happen?
Further Information
My Azure Functions App and Blazor WASM client are not part of the same project and are hosted on different sub-domains of Azure hypotheticalapi.azurewebsites.net and hypotheticalweb.azurewebsites.net.
The web client application registration has API Permissions for the API and the API has an application registration which exposes itself with the scope that the client app has permissions for.
Again, the API and Web app work individually. I just don't seem able to get them to talk.
I have been following the "ASP.NET Core Blazor WebAssembly additional security scenarios" documentation but after several attempts I keep coming back to the error:
Microsoft.JSInterop.JSException: invalid_grant: AADSTS65001:
The user or administrator has not consented to use the application with ID 'e40aabb0-8ed5-4833-b50d-ec7ca4e07996' named 'BallerinaBlazor5Wasm'.
Send an interactive authorization request for this user and resource.
Even though I have revoked/deleted the client's permissions on the API, it has never repeated asking for consent. Is there a way I should clear the consent I previously gave? No idea how I might do that.
This GitHub Issue appears to be relevant.
I was stuck for the last two weeks with the same error code in the same setting: Blazor WASM talking to an AAD secured Azure Functions app.
What appeared to be a problem in my case was the scopes that I was listing in the http request when contacting AAD identification provider endpoints. Almost all examples I came across use Microsoft Graph API. There, User.Read is the scope that is given as an example. My first though was that even when I am contacting my own API I have to include the User.Read scope in the request because I was reasoning that this scope is necessary to identify the user. However, this is not the case and the only scope that you have to list when you call the authorize and token endpoints is the one that you exposed under the "Expose an API blade" in your AAD app registration.
I am using the OAuth2 authorization code in my example and not the implicit grant. Make sure that in the manifest of your API registration you have set "accessTokenAcceptedVersion": 2 and not "accessTokenAcceptedVersion": null. The latter implies the use of implicit flow as far as I know.
The scope the I exposed in my API is Api.Read. You can expose more scopes if you need but the point is that you only ask for scopes that you exposed.
I also have both following options unticked (i.e. no implicit flow). However, I tried with selecting "ID token" and it still worked. Note that the "ID token" option is selected by default if you let the Azure Portal create your AAD app registration from your function app Authentication blade.
Blazor code
Program.cs
This code has to be added.
builder.Services.AddScoped<GraphAPIAuthorizationMessageHandler>();
builder.Services.AddHttpClient("{NAME}",
client => client.BaseAddress = new Uri("https://your-azure-functions-url.net"))
.AddHttpMessageHandler<GraphAPIAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("{NAME}"));
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
// NOTE: no "api://" when providing the scope
options.ProviderOptions.DefaultAccessTokenScopes.Add("{you API application id}/{api exposed scope}");
});
appsetting.json
"AzureAd": {
"Authority": "https://login.microsoftonline.com/{aad tenant id}",
"ClientId": "{application id of your blazor wasm app}",
"ValidateAuthority": true
}
GraphAPIAuthorizationMessageHandler.cs
Note that this class can have a different name. you'll then also reference a different name in Program.cs.
public class GraphAPIAuthorizationMessageHandler : AuthorizationMessageHandler
{
public GraphAPIAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "https://your-azure-functions-url.net" },
// NOTE: here with "api://"
scopes: new[] { "api://{you API application id}/{api exposed scope}" });
}
}
I hope this works. If not, let me know.
At least you need to get the access token, then use the token to call the function api. In this case, if you want to get the token in only one step, you could use the client credential flow, MSAL sample here, follow every part on the left to complete the prerequisites.
The following are the approximate steps(for more details, you still need to follow the sample above):
1.Create a new App registration and add a client secret.
2.Instantiate the confidential client application with a client secret
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithClientSecret(config.ClientSecret)
.WithAuthority(new Uri(config.Authority))
.Build();
3.Get the token
string[] scopes = new string[] { "<AppId URI of your function related AD App>/.default" };
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
4.Call the function API
httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
// Call the web API.
HttpResponseMessage response = await _httpClient.GetAsync(apiUri);
...
}

Get SharePoint Office 365 data using Client Id

I am accessing SharePoint online list data using the following code but i get the following error.
The remote server returned an error: (403) Forbidden.
The web forms application is running in Azure and uses Azure authentication. I would like to use the same credentials for authentication
string siteUrl = "[url]";
string aadAppId = "[appid]";
string clientSecret = "[redacted]";
OfficeDevPnP.Core.AuthenticationManager authManager = new OfficeDevPnP.Core.AuthenticationManager();
ClientContext context = authManager.GetAzureADNativeApplicationAuthenticatedContext(siteUrl, aadAppId, appurl, null, AzureEnvironment.Production);
if (context != null)
{
Web web = context.Web;
context.Load(web);
context.ExecuteQuery();
}
I do not want to access the SharePoint using the user credentials.
I have enabled AAD Azure authentication on the web app. The web app authenticates with Azure credentials.
On the CORS setting i have also set the Domain Url to "https://domainname.sharepoint.com";
In the manage permissions section of AAD app I have given the app Permission to read and write sharepoint list and web data.
You example is attempting to retrieve a token as a native client application, which is an app that cannot keep a secret and cannot authenticate by itself. (That's why you'll notice you never use clientSecret.)
If you want to use OfficePnP, you can try one of the AuthenticationManager.GetAzureADAppOnlyAuthenticatedContext methods to authenticate in the app-only context. Note that they all require a certificate for authentication, rather than a secret.

Resources