Search code examples
c#azureazure-active-directoryazure-functionssingle-sign-on

Correct SSO OAuth flow for azure web application and azure function web API


I find federated/Oauth authentication highly confusing. I have a web application that is using an azure AD to authenticate users. I also have a azure function that authenticates against the same Azure AD. I can reach both independently and authenticate and access them.

But now I want the web application to make requests to the azure function as the user logged in to the web application. This is so that the API can return user specific information.

What Oauth ?flow is required and how do I implement it.

Though the question for now is about a single tenant app registrations. I may need to change that to multi-tenant in the future. Optionally consider this for the answer.

The Azure function API is running .net6 ISOLATED. So much of the easy config wizards don't work for that in the azure admin portal.


Solution

  • The situation you're describing involves two key phases of authentication:

    1. Verifying the user's identity through Azure AD within the web application (which you've already completed), and
    2. Conveying that user's identity to the Azure function to authenticate the web application's requests made on behalf of the user.

    The most suitable OAuth 2.0 mechanism for this scenario is the On-Behalf-Of (OBO). This permits your web application to obtain an access token for the Azure function (the 'downstream' API), representing the user.

    Here's a brief summary of how this process operates:

    1. The user logs into the web application, which subsequently retrieves an access token from Azure AD.
    2. If the web application needs to call the Azure function, it provides Azure AD with the user's access token and requests a new one for the Azure function.
    3. Azure AD verifies the user's access token and generates a new one for the Azure function.
    4. The web application employs this newly obtained token to call the Azure function, acting on behalf of the user.

    some modifications are needed in your Azure AD app registrations for both your web application and Azure function. Additionally, adjustments are required in the token retrieval logic in your web application.

    1. Adjustments to the app registration:

    In the Azure portal, locate the app registration for your web application.

    • Navigate to the 'API permissions' blade and add a new permission.
    • Instead of selecting an API provided by Microsoft, click on the 'APIs my organization uses' tab and locate your Azure function's app registration.
    • Add the 'access_as_user' permission.

    This adjustment enables your web application to request tokens for the Azure function.

    2. Modifications to token retrieval:

    In the logic for obtaining tokens within your web application, when the Azure function needs to be called:

    • Employ the 'AcquireTokenOnBehalfOf' method (or an equivalent depending on the SDK you're using) to secure a token for the Azure function.
    • The arguments for this method should include the user's access token and the Azure function's scope (which is typically '/access_as_user', where '' refers to the Application (client) ID URI of the Azure function's app registration).

    Here's an example using the 'Microsoft.Identity.Web' library:

    string[] scopes = new string[] { "<AppIdUri>/access_as_user" };
    
    // Assuming 'userAccessToken' is the token of the authenticated user
    AuthenticationResult result = await _tokenAcquisition
        .AcquireTokenOnBehalfOf(scopes, new UserAssertion(userAccessToken))
        .ExecuteAsync();
    
    // Use 'result.AccessToken' to call the Azure function
    

    3. Changes to the Azure Function:

    Usually, the 'Microsoft.Identity.Web' library would be used to validate incoming tokens. However, Azure Functions' isolated model doesn't directly support it. Instead, manual validation of the tokens is required.

    To do this, use the 'System.IdentityModel.Tokens.Jwt' library. This requires you to:

    • Retrieve the signing keys from Azure AD's metadata endpoint (https://login.microsoftonline.com//v2.0/.well-known/openid-configuration).
    • Validate the token's signature, issuer, audience, and other claims.

    Regarding multi-tenancy, if you want to make your application accessible to users from various tenants, you should:

    • Alter the 'Supported account types' setting in your app registrations to 'Accounts in any organizational directory'.
    • Update the token validation logic in your Azure function to accept tokens from multiple issuers.

    You also have to consider if your web application is an application which is written as a Single Page Web App or Is using Backend server side code for authenticating user or making a the call to azure function. Above Approach is suitable if your web application is using server side code to all the azure function. If you are having the web application which is a Single Page Application, you will have to adjust your approach as you may not want to expose the client id and secret etc via browser java script code.

    For a SPA, you would typically use the OAuth 2.0 Implicit Grant or Authorization Code with PKCE (Proof Key for Code Exchange) flow to authenticate the user.

    With this approach, the SPA can obtain an access token for the user, which can then be used to call the Azure function.

    However, SPA cannot use the On-Behalf-Of (OBO) flow, as this requires the application to have access to the client secret or a certificate, which is not suitable for a public client like a SPA.

    In this scenario you should:

    1. Implement the Azure function to accept tokens from both the web application (in case of a server-side app) and the user (in case of a SPA). This means validating the scp or roles claim in the token to ensure the user has the necessary permissions.

    2. Use the user's access token to call the Azure function directly from the SPA. The Azure function should validate the token and use the sub or oid claim to determine the user's identity and return user-specific information.

    This way, the Azure function can provide user-specific information in both scenarios: when called directly from the SPA and when called from the web application on behalf of the user.