0

Note: Both Azure Function and the SPFx WebPart mentioned below are written in NodeJS/JavaScript. None of them are in C#.

I have an Azure Function (secured by AAD: App Registration) which is being called by AadHttpClient via SPFx WebPart on a SharePoint page. The SPFx codes look like this:

return new Promise<void>((resolve: () => void, reject: (error: any) => void): void => {
    this.context.aadHttpClientFactory.getClient("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") // <--- This is the AAD Client App Id.
    .then((client: AadHttpClient): void => {
        
        client.post("https://myAzureFunctionName.azurewebsites.net/api/HttpTrigger1", AadHttpClient.configurations.v1, {
            body: JSON.stringify({
                data: someData
            })
        })
        .then((res: HttpClientResponse): Promise<any> => {
            return res.json();
        })
        .then((response: any): void => {
            console.log("SUCCESSFUL API RESPONSE:", response); // <--- At this point, I get the respond back from the Azure Function, successfully.
            resolve();
        }, (err: any): void => {
            console.error(err);
        });

    }, err => reject(err));
});

It is working fine except from the Azure Function end, I don't know how to properly detect who/which current SharePoint User is calling this API. The only dirty trick I can use is, of course, to attach the User Information, such as Email Address, (retrieved from _spPageContextInfo object) into the AadHttpClient API call, to the Azure Function.

Question

  • Is there a proper/authentic way where I can detect the caller (the currently logged in, end-user of SPFx WebPart) through the AadHttpClient, from the Azure Function end? So that I can use the user's Email Address further in the Azure Function.

Appreciate the helps in advance.

2 Answers 2

1
  • You can obtain identity details using ClaimsPrincipal object.

  • The most straightforward method is to obtain the ClaimsPrincipal object from the Function's binding parameters.

  • Include the ClaimsPrincipal as an additional parameter in the function signature.

  • The object will be automatically injected, similar to how ILogger is injected.

  • Sample code

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System.Security.Claims;

// DEMO: Get ClaimsPrincipal as a binding parameter
namespace ClaimsDemo.Function
{
    public static class ClaimsDemo
    {
        // Pass ClaimsPrincipal parameter in the function signature
        [FunctionName("ClaimsDemo")]
        public static IActionResult Run
            ([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
             HttpRequest req, ILogger log, ClaimsPrincipal claimIdentity)
        {
            log.LogInformation("User ID: " + claimIdentity.Identity.Name);
            
            log.LogInformation("Claim Type : Claim Value");

            foreach (Claim claim in claimIdentity.Claims)
            {
                log.LogInformation(claim.Type + " : " + claim.Value + "\n");
            }

            return new OkObjectResult("Success");
        }
    }
}

User Claims from the Request Headers

  • App Service passes user claims to the app by using special request headers.
X-MS-CLIENT-PRINCIPAL-ID --User ID
X-MS-CLIENT-PRINCIPAL-NAME --User Name
X-MS-CLIENT-PRINCIPAL-IDP --Identity Provider's ID
X-MS-CLIENT-PRINCIPAL --Claims
//Extract User ID and Claims from the request headers
var principal_name = req.Headers["X-MS-CLIENT-PRINCIPAL-NAME"].FirstOrDefault();
var principal_Id = req.Headers["X-MS-CLIENT-PRINCIPAL-ID"].FirstOrDefault();
string easyAuthProvider = req.Headers["X-MS-CLIENT-PRINCIPAL-IDP"].FirstOrDefault();
string clientPrincipalEncoded = req.Headers["X-MS-CLIENT-PRINCIPAL"].FirstOrDefault();
4
  • Thanks for the helps. I guess yours are C# codes while I'm a TypeScript/JS person. But I'll try to find the TypeScript equivalent to your codes and see if the approach works. Will let you know later. Thanks again. Sep 28, 2022 at 10:15
  • 1
    You can access the user details using request header properties. use property mentioned in below comment Sep 28, 2022 at 10:42
  • 1
    X-MS-CLIENT-PRINCIPAL-ID --User ID X-MS-CLIENT-PRINCIPAL-NAME --User Name X-MS-CLIENT-PRINCIPAL-IDP --Identity Provider's ID X-MS-CLIENT-PRINCIPAL --Claims Sep 28, 2022 at 10:42
  • Hi @Rajat, reading the request headers worked! I can see the user's principle id and Email Address using (X-MS-CLIENT-PRINCIPAL-ID and ID X-MS-CLIENT-PRINCIPAL-NAME) headers! Not sure how secure is this approach. BUT IT SIMPLY WORKS OUT OF THE BOX! THANK YOU!!! (You can post this as a new Answer or edit your answer above, so that I can mark it as accepted answer!) Sep 28, 2022 at 12:21
0

You could make a call to the SharePoint REST API or the Microsoft Graph to get the full user profile in your Azure Function.

REST: /_api/SP.UserProfiles.PeopleManager/GetMyProperties

Graph: /me

To get the bearer token needed to make the call to get the user profile, you use the ConfidentialClientApplication.AcquireTokenOnBehalfOf method. You can do this using the following C# code inside your Azure Function. I don't know how to do the equivalent in Node.js.

var tenantName = "your tenant name";
var impersonationToken = req.Headers.Authorization.Parameter;

var clientId = "app client Id";
var clientSecret = "app client secret";
var authority = $"https://login.microsoftonline.com/{tenantName}.onmicrosoft.com";
var userAssertion = new UserAssertion(impersonationToken);

var azureApp = ConfidentialClientApplicationBuilder.Create(clientId)
    .WithAuthority(authority)
    .WithClientSecret(clientSecret)
    .Build();

var scopes = new string[] { "User.Read" };
var authResult = await azureApp.AcquireTokenOnBehalfOf(scopes, userAssertion)
    .ExecuteAsync();
var token = authResult.AccessToken;
2
  • Hi @Rob Windsor, thank you for helping out. Afaik, calling to REST or Graph APIs /me as a user requires Bearer Token, right? May I know how do I seek for logged user's Bearer Token from the Azure Function point of view, please? Sep 28, 2022 at 12:24
  • I updated my answer with the code to get the token Sep 28, 2022 at 13:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.