2

I'm using the ASP.NET OWIN/Katana OAuthAuthorizationServer middleware and have run into a snag.

I have some sites trying to get authorization tokens and created an /oauth/authorize endpoint. However, these requests are coming from a SPA (Angular) and will often have a fragment (#) in the redirect URL.

When the request is made, it will set the redirect_uri to that URL, but URL-encoded (so # changes to %23). However, whenever %23 comes across in the URL, the status is always set to 400 and I can't seem to prevent this in any way... Can't override anything and no Web.config reserved bad character changes, etc.

Thus, I tried to just replace it with some placeholder and then redirect back to itself. This worked fine. However, I can't seem to undo my URL change. I need to put # back in the URL and redirect to that, but the OWIN middleware completely ignores any attempts at changing the URL back... Anyone have any ideas?

huysentruitw
  • 27,376
  • 9
  • 90
  • 133
Daniel Lorenz
  • 4,178
  • 1
  • 32
  • 39

2 Answers2

4

This is a limitation imposed by RFC 6749 Section 3.1.2:

The endpoint URI MUST NOT include a fragment component.

The OpenID Connect specification (and thus the popular IdentityServer3/4), which builds upon the OAuth2 spec, seems not to have this limitation, so you could either switch to OIDC or bend the OAuth2 spec a little bit :)

In the OWIN middleware the existence of the fragment is checked in OAuthAuthorizationServerHandler.

A work-around is to bypass this check by replacing the %23 denoting the fragment by a placeholder and patch the location redirect header before it gets send to the browser.

To replace the incoming %23 with a placeholder, you can override the providers MatchEndpoint method:

internal class OAuth2AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    public override Task MatchEndpoint(OAuthMatchEndpointContext context)
    {
        if (context.Request.Path.StartsWithSegments(context.Options.AuthorizeEndpointPath)
            && context.Request.QueryString.HasValue)
        {
            context.Request.QueryString = new QueryString(
                context.Request.QueryString.Value.Replace("%23", "__fragment__"));
        }

        return base.MatchEndpoint(context);
    }
}

The other part is trickier as you can't seem to do it by default from the provider class because the redirectUri is kept internally on the OAuthValidateClientRedirectUriContext.

A hacky way is to use reflection and modify this RedirectUri in the providers ValidateClientRedirectUri method:

public override async Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
    // Test if the redirect uri is allowed, if not call context.SetError("invalid_client");

    var redirectUri = context.RedirectUri.Replace("__fragment__", "#");
    var setter = context.GetType().GetProperty(nameof(context.RedirectUri))?.GetSetMethod(true);
    setter?.Invoke(context, new[] { redirectUri });
    context.Validated(redirectUri);
}

Another way is to add a simple middleware to the chain (before the OAuth2 middleware) that monitors the response (so after awaiting the next middleware) and patches the Location header where needed. This is less tricky than setting a private property, but now the patch is spread over 2 different places which is less maintainable.

With above work-around a redirect URI like

https://server.com/#spa/path/123

note that you will pass a URL-encoded version to the authorize endpoint redirect_uri param

will result in a redirect to

https://server.com/#spa/path/123&access_token=<the_access_token>
Community
  • 1
  • 1
huysentruitw
  • 27,376
  • 9
  • 90
  • 133
0

Part after hash sign https://en.wikipedia.org/wiki/Fragment_identifier never to be sent to server - see for example Can I read the hash portion of the URL on my server-side application (PHP, Ruby, Python, etc.)? and many similar questions here.

What you can do is to post-process your placehodler on client-side via Java Script rather than trying to redirect to URL with # in it.

Community
  • 1
  • 1
Lanorkin
  • 7,310
  • 2
  • 42
  • 60
  • The # is never sent, but this is a redirect_url that got encoded, so it sent %23 which does come across. Though, maybe that is why it is getting rejected by the server since that could end up sending private information that shouldn't come across? – Daniel Lorenz Mar 17 '17 at 13:42
  • This is not the correct answer as the URL in question doesn't contain a `#`. This is a common use-case when using hash routing in a SPA + OAuth2/OIDC scenario. – huysentruitw Feb 21 '18 at 20:29
  • @WouterHuysentruit If you read carefully question is "I need to put # back in the URL and redirect to that" – Lanorkin Feb 21 '18 at 20:43
  • A server can perfectly redirect to a URL that includes a hash fragment, that is not the problem. The problem is getting the URL encoded hash fragment in the `redirect_uri` parameter, which seems not to be supported by the OWIN middleware. But is supported by f.e. [IdentityServer3](https://github.com/IdentityServer/IdentityServer3/issues/1319). So again, this answer is not answering this question. The question how to make the %23 work with OWIN middleware is still unanswered. – huysentruitw Feb 21 '18 at 20:50
  • I think we ended up being forced to replace it on the client side. I changed it to "_ . ._" or something that shouldn't normally come across. Then the angular apps intercept this and change it before allowing the routing to go forward. So ultimately, this did answer my question. :) – Daniel Lorenz Feb 22 '18 at 03:22
  • Well I was confronted with the same problem but didn't want to let the front-end suffer from this limitation so I've figured it out myself (see [answer](https://stackoverflow.com/a/48929613/1300910)). Also tweeted to the author of IdSrv3 to figure out if they ignored that part of the spec or if it's a difference between OAuth2 vs OIDC. – huysentruitw Feb 22 '18 at 15:12