Search code examples
azureazure-active-directoryazure-functionssession-cookiesazure-authentication

Calling azure function from client app in browser on behalf authorized user


My goal is to have Angular 8 SPA with a serverless backend represented by multiple azure function apps using OAuth from Facebook, Google...

I have a problem with calling azure functions on behalf of authorized users. Function sees these calls as anonymous users.

From the browser, function returns authorized user name, from browser app call it returns 'no name' that mean user is not authorized.

My guess is that the session from myapp.azurewebsites.net is not visible in my app that is in the localhost (it can be any other domain). Also, I can't provide session in the request to function endpoint.

So, what is the way to authorize users and call azure functions from the client app in other domain? Or it is possible only with the manual implementation of JWT tokens with spreaded logic across all functions? Also, I do not want to pay to third-party services like Auth0 or even AAD.

Jim Xu, suggested a way that works, but not for my case. Disadvantages I see:

  • With this approach, I can't see the user name and email in the claims principal. Also not sure what can I use as user Id.
  • Login logic will be spread across all apps that are using the API.
  • Will need to store FB token to authenticate all function apps

I am looking for answers to such questions:

  1. Is there backend driven authentication for my case?
  2. Is there an ability to setup post_login_redirect_url to receive a token for service also?
  3. Maybe it is possible to set up via https://resources.azure.com/ ?
  4. Is it possible to share access token for multiple function apps? (In this way, UI logic will be simplified to make get to app/.auth/login/provider and then save a service token.)

My code sample and configurations:

Here is a client app main parts I am using to call:

<button (click)="call('https://myapp.azurewebsites.net/Function1')">CallWithoutAuth</button>
<button (click)="call('https://myapp.azurewebsites.net/Function2')">CallWithAuth</button>

<a href="https://myapp.azurewebsites.net/.auth/login/facebook?post_login_redirect_url=http%3A%2F%2Flocalhost%3A5000" target="_blank">Log in with Facebook</a>

Body of call function:

var url = 'my azure func url with auth via facebook';
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
  if (this.readyState == 4 && this.status == 200) {
    console.log(this.responseText);
  }
};
xhttp.onerror = function(e){console.log(e, this)};
xhttp.open("GET", url, true);
xhttp.send();

Function code:

public static async Task<IActionResult> Run(
 [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "Func2")] HttpRequest req, 
 ClaimsPrincipal claimsPrincipal)
{
    var name = claimsPrincipal?.Identity?.Name;
    return (ActionResult)new OkObjectResult($"Hello, {name ?? "no name"}");
}

Here are functions app configs:

enter image description here

CORS config:

enter image description here

Fasebook config:

enter image description here


Solution

  • According to my test, we can use the following steps to call the Azure function projected by facebook

    1. Integrate Facebook into your angular application. after doing that, we can login facebook and get a accessToken. For more details about how to implement it, please refer to the article.

      For example

      a. add the application URL into Valid OAuth Redirect URIs

      b. add the following code in app.component.ts

      
      export class AppComponent {
        title = 'web';
        fbLibrary() {
      
          (window as any).fbAsyncInit = function() {
            window['FB'].init({
              appId      : '<app id you use to project azure function>',
              cookie     : true,
              xfbml      : true,
              version    : 'v3.1'
            });
            window['FB'].AppEvents.logPageView();
          };
      
          (function(d, s, id){
             var js, fjs = d.getElementsByTagName(s)[0];
             if (d.getElementById(id)) {return;}
             js = d.createElement(s); js.id = id;
             js.src = "https://connect.facebook.net/en_US/sdk.js";
             fjs.parentNode.insertBefore(js, fjs);
           }(document, 'script', 'facebook-jssdk'));
      
      }
      ngOnInit() {
        this.fbLibrary();
      }
        login() {
      
          window['FB'].login((response) => {
      
      
              if (response.authResponse) {
               //get access token
                var accessToken=response.authResponse.accessToken;
                console.log('login response',accessToken);
                window['FB'].api('/me', {
                  fields: 'last_name, first_name, email'
                }, (userInfo) => {
      
                  console.log("user information");
                  console.log(userInfo);
                });
      
              } else {
                console.log('User login failed');
              }
          }, {scope: 'email'});
      }
      }
      
      
      

      c. add login button to html

    2. Call the following request to exchange this accessToken for an 'App Service Token'

    Request

       POST https://<appname>.azurewebsites.net/.auth/login/aad HTTP/1.1
      Content-Type: application/json
    
      {"access_token":"<token>"}
    
    

    Response

    {
        "authenticationToken": "...",
        "user": {
            "userId": "sid:..."
        }
    }
    

    enter image description here

    1. call the azure function
    Get https://myapp.azurewebsites.net/Function1
    X-ZUMO-AUTH: <authenticationToken_value>
    

    enter image description here For further information, please refer to https://learn.microsoft.com/en-us/azure/app-service/app-service-authentication-how-to.