Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting the redirect_uri in Asp.Net Identity

I am trying to set the redirect_uri for the facebook login with Asp.Net Identity. However, the GetExternalLogin REST method in the AccountController is only triggered if the redirect_uri is '/'. If I add anything else it does not trigger GetExternalLogin, the browser only shows error: invalid_request.

However the url contains the redirected parameter as it should e.g. if I add the redirect_uri as http://localhost:25432/testing

the response URL looks like this:

http://localhost:25432/api/Account/ExternalLogin?provider=Facebook&response_type=token&client_id=self&redirect_uri=http%3A%2F%2Flocalhost%3A25432%2Ftesting&state=0NctHHGq_aiazEurHYbvJT8hDgl0GJ_GGSdFfq2z5SA1

and the browser window shows: error: invalid_request
Any idea why this works only when redirecting to '/' but not to any other url´s?

like image 265
doorman Avatar asked Dec 19 '13 22:12

doorman


People also ask

How is the redirect URI for a web application computed?

In order to avoid customers to have to update the redirect URI in the code when they deploy their Web apps, the redirect URI is computed automatically by ASP.NET Core (part of the auth code flow), and also by Microsoft.Identity.Web (in TokenAcquisition.BuildConfidentialClientApplicationAsync ).

How to override openidconnect redirect Uri?

The openIDConnect redirect URI is computed by ASP.NET Core, but can be overriden by subscribing to the OpenIdConnect OnRedirectToIdentityProvider event and by setting the context.ProtocolMessage.RedirectUri property to the desired redirect URI. Same problem for the post logout redirect URI used in global sign-out.

How do I set an HTTP-based redirect Uri in Azure App registration?

To add redirect URIs with an HTTP scheme to app registrations that sign in work or school accounts, use the application manifest editor in App registrations in the Azure portal. However, though it's possible to set an HTTP-based redirect URI by using the manifest editor, we strongly recommend that you use the HTTPS scheme for your redirect URIs.

Should I register multiple redirect Uris?

Do not register multiple redirect URIs where only the port differs. The login server will pick one arbitrarily and use the behavior associated with that redirect URI (for example, whether it's a web -, native -, or spa -type redirect).


2 Answers

For anyone else that might run into this issue: the problem is when you take (copy) the ApplicationOAuthProvider.cs from the Visual Studio SPA template and it is there where this code is:

public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
    if (context.ClientId == _publicClientId)
    {
        var expectedRootUri = new Uri(context.Request.Uri, "/");

        if (expectedRootUri.AbsoluteUri == context.RedirectUri)
        {
            context.Validated();
        }
    }

    return Task.FromResult<object>(null);
}

This will obviously block any redirect_uri that doesn't look like http://localhost/ or http://example.com/ so for instance http://example.com/home won't work.

Now this below is the source for InvokeAuthorizeEndpointAsync in Katana which does all the work and you can see it calls into any custom OAuthProvider that might be registered for this MVC/Web API application (this registration typically happens in Startup.Auth.cs):

private async Task<bool> InvokeAuthorizeEndpointAsync()
{
    var authorizeRequest = new AuthorizeEndpointRequest(Request.Query);

    var clientContext = new OAuthValidateClientRedirectUriContext(
        Context,
        Options,
        authorizeRequest.ClientId,
        authorizeRequest.RedirectUri);

    if (!String.IsNullOrEmpty(authorizeRequest.RedirectUri))
    {
        bool acceptableUri = true;
        Uri validatingUri;
        if (!Uri.TryCreate(authorizeRequest.RedirectUri, UriKind.Absolute, out validatingUri))
        {
            // The redirection endpoint URI MUST be an absolute URI
            // http://tools.ietf.org/html/rfc6749#section-3.1.2
            acceptableUri = false;
        }
        else if (!String.IsNullOrEmpty(validatingUri.Fragment))
        {
            // The endpoint URI MUST NOT include a fragment component.
            // http://tools.ietf.org/html/rfc6749#section-3.1.2
            acceptableUri = false;
        }
        else if (!Options.AllowInsecureHttp &&
            String.Equals(validatingUri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase))
        {
            // The redirection endpoint SHOULD require the use of TLS
            // http://tools.ietf.org/html/rfc6749#section-3.1.2.1
            acceptableUri = false;
        }
        if (!acceptableUri)
        {
            clientContext.SetError(Constants.Errors.InvalidRequest);
            return await SendErrorRedirectAsync(clientContext, clientContext);
        }
    }

    await Options.Provider.ValidateClientRedirectUri(clientContext);

    if (!clientContext.IsValidated)
    {
        _logger.WriteVerbose("Unable to validate client information");
        return await SendErrorRedirectAsync(clientContext, clientContext);
    }

    var validatingContext = new OAuthValidateAuthorizeRequestContext(
        Context,
        Options,
        authorizeRequest,
        clientContext);

    if (string.IsNullOrEmpty(authorizeRequest.ResponseType))
    {
        _logger.WriteVerbose("Authorize endpoint request missing required response_type parameter");
        validatingContext.SetError(Constants.Errors.InvalidRequest);
    }
    else if (!authorizeRequest.IsAuthorizationCodeGrantType &&
        !authorizeRequest.IsImplicitGrantType)
    {
        _logger.WriteVerbose("Authorize endpoint request contains unsupported response_type parameter");
        validatingContext.SetError(Constants.Errors.UnsupportedResponseType);
    }
    else
    {
        await Options.Provider.ValidateAuthorizeRequest(validatingContext);
    }

    if (!validatingContext.IsValidated)
    {
        // an invalid request is not processed further
        return await SendErrorRedirectAsync(clientContext, validatingContext);
    }

    _clientContext = clientContext;
    _authorizeEndpointRequest = authorizeRequest;

    var authorizeEndpointContext = new OAuthAuthorizeEndpointContext(Context, Options);

    await Options.Provider.AuthorizeEndpoint(authorizeEndpointContext);

    return authorizeEndpointContext.IsRequestCompleted;
}

This is key:

 await Options.Provider.ValidateClientRedirectUri(clientContext);

So your solution is to change how the ValidateClientRedirectUri performs the validation - the default SPA implementation is, as you can see, very naive.

There's lots of ppl having issues with SPA mainly because it lacks any kind of useful information and I mean that both for ASP.NET Identity and OWIN stuff and with regards to what is going on within KnockoutJS implementation.

I wish Microsoft would provide more comprehensive docs for these templates because anyone who will try to do anything a bit more complex will run into issues.

I've spent hours on this, digging into OWIN (Katana) source code thinking it is the above implementation that blocks my redirect URIs but it was not, hopefully helps someone else too.

HTH

like image 75
mare Avatar answered Sep 20 '22 04:09

mare


The problem is that GetExternalLogin registered as OAuthOptions.AuthorizeEndpointPath which used for app.UseOAuthBearerTokens(OAuthOptions). This configuration puts validation on arguments of endpoint.

if (!Uri.TryCreate(authorizeRequest.RedirectUri, UriKind.Absolute, out validatingUri))
{
    // The redirection endpoint URI MUST be an absolute URI
}
else if (!String.IsNullOrEmpty(validatingUri.Fragment))
{
    // The endpoint URI MUST NOT include a fragment component.
}
else if (!Options.AllowInsecureHttp &&
                    String.Equals(validatingUri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase))
{
    // The redirection endpoint SHOULD require the use of TLS
}

And you should pass "Authorize endpoint request missing required response_type parameter" and "Authorize endpoint request contains unsupported response_type parameter"

like image 25
Misha Dev Avatar answered Sep 22 '22 04:09

Misha Dev