aspnet / AspNetKatana

Microsoft's OWIN implementation, the Katana project
Apache License 2.0
967 stars 334 forks source link

Clicking links from MS Office apps: "IDX10311: RequireNonce is 'true' (default) but validationContext.Nonce is null." #78

Closed smichtch closed 7 years ago

smichtch commented 7 years ago

When users click on links in documents from native MS Office apps (notably Word, Excel, PowerPoint but not Outlook or OneNote) and the linked web app is using the OpenIdConnectMiddleware this error pop up:

[OpenIdConnectProtocolInvalidNonceException: IDX10311: RequireNonce is 'true' (default) but validationContext.Nonce is null. A nonce cannot be validated. If you don't need to check the nonce, set OpenIdConnectProtocolValidator.RequireNonce to 'false'.]
   Microsoft.IdentityModel.Protocols.OpenIdConnectProtocolValidator.ValidateNonce(JwtSecurityToken jwt, OpenIdConnectProtocolValidationContext validationContext) in c:\workspace\WilsonForDotNet45Release\src\Microsoft.IdentityModel.Protocol.Extensions\OpenIdConnectProtocolValidator.cs:329
   Microsoft.IdentityModel.Protocols.OpenIdConnectProtocolValidator.Validate(JwtSecurityToken jwt, OpenIdConnectProtocolValidationContext validationContext) in c:\workspace\WilsonForDotNet45Release\src\Microsoft.IdentityModel.Protocol.Extensions\OpenIdConnectProtocolValidator.cs:216
   ...

The other symptom is that two tabs open in the browser, one with the callback endpoint (showing the IDX10311 error) and the other tab with the app's default redirect URL (as if OpenIdConnectMessage.RedirectUri wasn't set).

Otherwise auth works fine including in-browser links from external sites and even same office document opened HTML versions of Office. Also seems to be browser independent and repros in Edge, Chrome, FF.

The root cause is that office apps are doing background pre-fetch requests to test the link in a sandboxed browser before launching a real browser for the user. The office app also follows the HTTP 302 redirects the AAD login page (the one which does a JS post-back to the reply URL) and launches the browser with the login page URL (instead of the initial link URL). However since the initial 302 redirect that set the nonce cookie happened in the sandboxed browser, the real browser doesn't have the nonce cookie to match the nonce in the login page's URL = the IDX10311 error.

Ultimately I think the real fix here is for Office to make the pre-fetches compatible with single-sign-on flows. However one way to mitigate this server-side is to intercept pre-fetch requests generated by the office apps and return blanket 200 OK responses (preempting the auth challenges/redirects). Implemented it as an OWIN middleware:

app.Use<MsOfficeLinkPrefetchMiddleware>();
app.UseOpenIdConnectAuthentication(...);

...

public class MsOfficeLinkPrefetchMiddleware : OwinMiddleware
{
    public MsOfficeLinkPrefetchMiddleware(OwinMiddleware next) : base(next) { }

    public override Task Invoke(IOwinContext context)
    {
        if (Is(context, HttpMethod.Get, HttpMethod.Head) && IsMsOffice(context))
        {
            // Mitigate by preempting auth challenges to MS Office apps' preflight requests and
            // let the real browser start at the original URL and handle all redirects and cookies.

            // Success response indicates to Office that the link is OK.
            context.Response.StatusCode = (int)HttpStatusCode.OK;
            context.Response.Headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
            context.Response.Headers["Pragma"] = "no-cache";
            context.Response.Headers["Expires"] = "0";
        }
        else if (Next != null)
        {
            return Next.Invoke(context);
        }

        return Task.CompletedTask;
    }

    private static bool Is(IOwinContext context, params HttpMethod[] methods)
    {
        var requestMethod = context.Request.Method;
        return methods.Any(method => StringComparer.OrdinalIgnoreCase.Equals(requestMethod, method.Method));
    }

    private static readonly Regex _msOfficeUserAgent = new Regex(
        @"(^Microsoft Office\b)|([\(;]\s*ms-office\s*[;\)])",
        RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);

    private static bool IsMsOffice(IOwinContext context)
    {
        var headers = context.Request.Headers;

        var userAgent = headers["User-Agent"] ?? string.Empty;

        return _msOfficeUserAgent.IsMatch(userAgent)
            || !string.IsNullOrWhiteSpace(headers["X-Office-Major-Version"]);
    }
}

Further notes/discussion/observations:

  1. Office apps use some kind of sandboxed browser to probe links embedded in documents and clicked by users. Fiddler does not detect these pre-fetch requests which made diagnosis more difficult, Wireshark does see them (unless they're HTTPS links).

  2. The first pre-flight request has a signature like:

    HEAD /original-url HTTP/1.1 Connection: Keep-Alive Authorization: Bearer User-Agent: Microsoft Office Word 2014 (16.0.8229) Windows NT 10.0 X-Office-Major-Version: 16 X-MS-CookieUri-Requested: t X-FeatureVersion: 1 X-IDCRL_ACCEPTED: t Host: localhost

    and can be fingerprinted by the User-Agent: Microsoft Office * or X-Office-Major-Version: * headers.

  3. Second pre-flight request has a different signature:

    GET /original-url HTTP/1.1 Accept: */* X-IDCRL_ACCEPTED: t User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; Win64; x64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; ms-office) UA-CPU: AMD64 Accept-Encoding: gzip, deflate Host: localhost Connection: Keep-Alive

    The only obvious unique marker is the ms-office comment or possibly the unusual Mozilla/4.0 version in the User-Agent header.

  4. The HEAD and GET request sequences DO NOT share cookies between each other or with the real user browser (i.e. pre-fetches are sandboxed). The pre-fetches do seem to follow cache directives.

  5. Couldn't find any info on the X-IDCRL_ACCEPTED header may also be useful for identifying these requests.

  6. Only other report of the same issue https://stackoverflow.com/questions/35047438/oauth-and-excel-links

Issues reporting similar symptoms, but different root causes/fixes: https://github.com/IdentityServer/IdentityServer3/issues/542 https://github.com/IdentityServer/IdentityServer3/issues/931 https://stackoverflow.com/questions/29378683/openidconnect-azure-website-hosted-in-an-iframe-within-dynamics-crm-online

Tratcher commented 7 years ago

Duplicate of https://github.com/aspnet/Security/issues/1252, closed as external.

Crossbow78 commented 6 years ago

So we have the Office team blaming the OpenId authentication implementation from Microsoft, and vice versa, a bit unfortunate situation.

Perhaps I'm missing something, but if MS Office would just do its prefetch request, and then pass the original hyperlink to the browser (instead of some mid-authentication link it ended up at), we wouldn't be having this issue.

Anyway...

We've applied @smichtch's work-around and it worked beautifully. But recently things changed again: when Excel is in protected view and the user clicks on a hyperlink inside that Excel file, Office will use a different user-agent header for its prefetch request that does not include any trace of Office. This effectively destroys the work-around.

So in that case we're again facing with the error:

IDX21323: RequireNonce is '[PII is hidden by default. Set the 'ShowPII' flag in IdentityModelEventSource.cs to true to reveal it.]'. OpenIdConnectProtocolValidationContext.Nonce was null, OpenIdConnectProtocol.ValidatedIdToken.Payload.Nonce was not null. The nonce cannot be validated. If you don't need to check the nonce, set OpenIdConnectProtocolValidator.RequireNonce to 'false'. Note if a 'nonce' is found it will be evaluated.

(Note that before updating to v4.0.0 of the Microsoft.Owin.Security.OpenIdConnect package, this used to match the error from the issue title.)

So we decided to give in and disable the Nonce validation with something like this in our Startup class:

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
            {
                (...)
                ProtocolValidator = new OpenIdConnectProtocolValidator
                {
                    RequireNonce = false
                },
                (...)

But now when clicking a link in Office, we get an OpenIdConnectProtocolException:

OpenIdConnectMessage.Error was not null, indicating an error. Error: 'invalid_request'. Error_Description (may be empty): 'AADSTS90014: The required field 'nonce' is missing.'

So now our application ignores the absence of the Nonce, but at some lower level in Azure it's still required...?

Is there any more we can do?

Also, I couldn't find the Office counterpart of this issue. Anyone know where to look?

scott7685 commented 6 years ago

Just to pile in and add additional context, we recently experienced the same issue -- Excel would open two tabs when clicking a link to our ASP.NET web app. The problem began after we changed over from forms identity authentication to using OpenIDConnect authentication with Azure ActiveDirectory.

We did find that the Microsoft EasyFix download tool would resolve it (link below), however we did not want to have to install that on every computer. Using smichtch's code above resolved the issue as well.

Microsoft EasyFix link https://support.microsoft.com/en-us/help/218153/error-message-when-clicking-hyperlink-in-office-cannot-locate-the-inte

Tratcher commented 6 years ago

Ah, I didn't know office had provided a workaround, great. @scott7685 since it's a reg key change you could deploy it via group policy or similar. You don't need the exe.

Prashant10 commented 6 years ago

Thanks for the solution guys! EasyFix worked for me too. Earlier, I was also facing the same problem. Do we know how can everyone get the similar fix? I mean will Microsoft push a WIndows Update or Office Update for this fix? If yes, then is there any way I can check when will it get rollout?