openiddict / openiddict-core

Flexible and versatile OAuth 2.0/OpenID Connect stack for .NET
https://openiddict.com/
Apache License 2.0
4.43k stars 520 forks source link

Endpoints 404 with Asp.net MVC 5 #989

Closed farlee2121 closed 4 years ago

farlee2121 commented 4 years ago

I'm trying to get OpenIddict running with Asp.Net 4.8 and MVC 5.

I first followed the OWIN example at https://kevinchalet.com/2020/03/03/adding-openiddict-3-0-to-an-owin-application/. I am using the the recommended AutoFac registration method.

I was able to get this working properly. The tutorial says that the same should apply for other hosts, including System.Web.

However, I cannot get through to the token endpoint (or authorization endpoint if I try to add that). It always returns a 404.
So far I've tried

My suspicion is that this is a routing issue, but I just can't seem to find the right lead to figure it out.

Are there any special requirements for using OpenIddict with MVC 5?

kevinchalet commented 4 years ago

First of all, are you sure your Startup is correctly invoked?

farlee2121 commented 4 years ago

I now feel quite silly. You, sir, are a champion.

kevinchalet commented 4 years ago

Don't hesitate to add some details on what was causing that. It may help other people πŸ˜ƒ

farlee2121 commented 4 years ago

Good point. I was working in a new MVC 5 project straight from the template, but I forgot to change the identity option from 'none' to individual accounts.

This means that the project doesn't come with Microsoft.Owin.Host.SystemWeb out of the box. It also doesn't install that library when you install the other requisite Owin packages or if you choose to add an Owin startup file through the 'add new' menu.

Installing Microsoft.Owin.Host.SystemWeb fixed the issue

kevinchalet commented 4 years ago

Makes total sense πŸ˜„

farlee2121 commented 4 years ago

2 things First, I've been digging through the issues a lot to understand some aspects of the framework. I really appreciate how patient and encouraging you are with people's questions.

Second, the example I found on challenging requests from event handlers doesn't seem to be current.

For posterity, I was able to achieve it with

var owinContext = HttpContext.Current.GetOwinContext();
var userInfo = owinContext?.Authentication?.User?.Identity;
if (userInfo == null || userInfo.IsAuthenticated == false)
{
    owinContext.Authentication.Challenge(relevant_auth_type);
    context.SkipRequest();
}
kevinchalet commented 4 years ago

I really appreciate how patient and encouraging you are with people's questions.

Thanks 🀟

Second, the example I found on challenging requests from event handlers doesn't seem to be current.

That's because the sample uses ASP.NET Core instead of OWIN/Katana (the authentication APIs have changed quite a lot in ASP.NET Core 2.0).

Your snippet looks good. I have 2 remarks, tho':

var manager = context.Transaction.GetOwinRequest()?.Context.Authentication ??
    throw new InvalidOperationException("The OWIN authentication manager couldn't be resolved.");

In case you'd prefer using the pass-through mode, here's an example of how to use it with an ASP.NET MVC 5 controller (it's for the token endpoint, but it works exactly the same way with the authorization endpoint):

using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Web;
using System.Web.Mvc;
using Microsoft.Owin.Security;
using OpenIddict.Abstractions;
using OpenIddict.Server.Owin;
using Owin;
using static OpenIddict.Abstractions.OpenIddictConstants;

namespace OpenIddictAspNetDemo.Controllers
{
    public class ConnectController : Controller
    {
        [HttpPost]
        public ActionResult Token()
        {
            var request = HttpContext.GetOwinContext()?.GetOpenIddictServerRequest() ??
                throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");

            if (request.IsPasswordGrantType())
            {
                // Validate the username/password parameters.
                if (!string.Equals(request.Username, "alice@wonderland.com", StringComparison.Ordinal) ||
                    !string.Equals(request.Password, "P@ssw0rd", StringComparison.Ordinal))
                {
                    return new ChallengeResult(
                        authenticationType: OpenIddictServerOwinDefaults.AuthenticationType,
                        properties: new AuthenticationProperties(new Dictionary<string, string>
                        {
                            [OpenIddictServerOwinConstants.Properties.Error] = Errors.InvalidGrant,
                            [OpenIddictServerOwinConstants.Properties.ErrorDescription] = "The username/password couple is invalid."
                        }));
                }

                var principal = new ClaimsPrincipal(new ClaimsIdentity(OpenIddictServerOwinDefaults.AuthenticationType));
                principal.SetClaim(Claims.Subject, "Bob");

                // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
                return new SignInResult((ClaimsIdentity) principal.Identity);
            }

            throw new InvalidOperationException("The specified grant type is not supported.");
        }

        internal class SignInResult : ActionResult
        {
            public SignInResult(ClaimsIdentity identity)
                => Identity = identity ?? throw new ArgumentNullException(nameof(identity));

            public ClaimsIdentity Identity { get; }

            public override void ExecuteResult(ControllerContext context)
                => context.HttpContext.GetOwinContext().Authentication.SignIn(Identity);
        }

        internal class ChallengeResult : ActionResult
        {
            public ChallengeResult(string authenticationType, AuthenticationProperties properties)
            {
                AuthenticationType = authenticationType;
                Properties = properties;
            }

            public string AuthenticationType { get; }
            public AuthenticationProperties Properties { get; }

            public override void ExecuteResult(ControllerContext context)
                => context.HttpContext.GetOwinContext().Authentication.Challenge(Properties, AuthenticationType);
        }
    }
}
farlee2121 commented 4 years ago

Ah. I figured it should be possible to get the Authentication context off of the event context. I first looked for it on Principle, got as far as context.Transaction.GetOwinRequest(), and got tripped up at Context. This is definitely a nicer solution.

HandleResponse() does work. I just misinterpreted the description thinking that it would prevent my login flow from taking over. It doesn't because the challenge writes a response rather than some internal call or middleware.

I was trying to avoid the pass through option, since I'm working with two company-owned sites that will always have an implicit grant. It's good to see that option laid out clearly out though.