Search code examples
azure-active-directoryazure-web-app-servicecontent-security-policyazure-front-dooreasy-auth

Content-Security-Policy settings when using built in Azure App Service Authentication (Easy Auth)


I have a Blazor Server app deployed to an Azure App Service using the built in authentication feature.

Traffic comes in via Azure Front Door, and a Content-Security-Policy is set (by the front door service) using an Overwrite rule that sets Content-Security-Policy to:

base-uri 'self'; default-src 'self' https://*.<myapp.com> wss://*.<myapp.com> https://dc.services.visualstudio.com/v2/track; object-src 'none'; script-src 'self' https://*.<myapp.com> https://az416426.vo.msecnd.net; img-src 'self' data:; style-src 'self'; frame-ancestors 'self'; form-action 'self'; upgrade-insecure-requests;

This works fine except for a single script which is involved in the authentication flow. According to Chrome dev tools the URL is still <myapp.com>, so I am unsure why it is being blocked. The script I am dealing with is:

<script>
    function redirectToLoginPage() {
        if (window.location.hash) {
            document.cookie = "PreLoginUrlFragment=" + window.location.hash + "; path=/";
        }

        window.location.replace('https://login.microsoftonline.com/common/oauth2/v2.0/authorize?response_type=code+id_token\u0026redirect_uri=https%3A%2F%2F<myapp.com>%2F.auth%2Flogin%2Faad%2Fcallback\u0026client_id=<myappid>\u0026scope=openid+profile+email\u0026response_mode=form_post\u0026nonce=<A-Nonce-That-Changes-Every-Time>');
    }
</script>

This page source is loaded in the client under my top level url - the interesting part is that this is in fact content presented by the Auth middleware container which runs inside the same VM on the App Service. This architecture is described in the Azure App Service auth documentation here: https://learn.microsoft.com/en-us/azure/app-service/overview-authentication-authorization#feature-architecture

The problem is that even though it is within the same host (though not the same container), the client somehow thinks it doesn't comply with the policy. I would expect it to be captured as 'self' based on the url, but it isn't... Example error from Chrome dev tools:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' https://*.<myapp.com> https://az416426.vo.msecnd.net". Either the 'unsafe-inline' keyword, a hash ('sha256-+3E1vW0Im9gtvTYo1w9L7vcEGlzuoxrOvvGJc+ABSZ4='), or a nonce ('nonce-...') is required to enable inline execution.

Normally for something like this I would add the hash to the policy - however the script contains a login destination URL with a nonce in it which is different every time the page loads, so the hash for the script is also different every time.

I cannot add a nonce to the script (or edit it in any way) because it's part of the auth middleware which I do not control.

I expected that by having both 'self' and the top-level domains of my app in the header that it would work. It currently only works if I add 'unsafe-inline' which is not an option due to security requirements.

The above CSP seems like it's really close to what I need, it's just this one script generating an error now (and unfortunately, preventing login, so it's a blocking problem).


Solution

  • Answering my own question in case it helps others.

    My solution involved moving the CSP into my application code (so into the meta tags in the shared _Layout component in a standard Blazor app) like below.

    This did not technically fix the root cause, but it did prevent my CSP from being applied to the pages served by the Auth container, and thus stop the error from occurring (possibly at the cost of some level of security).

    I made further tweaks to the policy after this to mop up some unrelated errors, but the important change was removing the frame-ancestors tag as that is not respected when put in meta tags:

    @using Microsoft.AspNetCore.Components.Web
    @namespace MyApp.Pages
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="Content-Security-Policy" content="base-uri 'self'; default-src 'self' ... //the rest of the policy" />
        <base href="~/" />
    ... //rest of the page content
    

    Then I updated the Azure Front Door rule to append (not overwrite) the frame-ancestors tag only.

    The net result is that login now works, but the CSP (with the exception of the frame-ancestors tag added by front door service) is not applied at all to content served by the Easy Auth container.

    This means that all of my own app content has the appropriate CSP applied, which is a good result overall!

    What I noticed while doing this, is that for whatever reason, the Auth container provided by Microsoft does not apply a CSP header of any sort to its own content, and neither do any of the external pages involved in the Azure AD Auth flow such as login.live.com and login.microsoftonline.com.

    I expected to see some sort of secure defaults (that work of course), but there's just nothing.

    I understand that an auth flow like this is a cross-site flow by nature, so it has to be loosened a bit, but I found the fact that there is no CSP applied AT ALL to the flow to be a bit strange, especially as the original window.location.replace target contains a nonce value, so who knows what that's actually used for...

    I could be misunderstanding how the auth flow is secured on this dimension, but as these pages/content are not in my control we are considering them out of scope for now. I may reach out to Microsoft for clarification on expected behavior and security settings.