NancyFx / Nancy

Lightweight, low-ceremony, framework for building HTTP based services on .Net and Mono
http://nancyfx.org
MIT License
7.15k stars 1.46k forks source link

Nancy and Okta OpenID Integration via OWIN #2892

Open OHAVM opened 6 years ago

OHAVM commented 6 years ago

I'm using the latest version of NancyFX with .NET Framework 4.7.1 in a project to create a RESTful Service. I'm finding that I am not authenticating the client with OpenID via Okta. Below is my Startup.cs code. If anyone could advise on why this is not working?


using Microsoft.Owin;
using Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.Configuration;
using System.Security.Claims;
using IdentityModel.Client;
using System;
using System.Collections.Generic;
using Microsoft.IdentityModel.Tokens;
using Nancy.Owin;
using Nancy;

[assembly: OwinStartup(typeof(ServiceFrameworkBase.Startup))]

namespace ServiceFrameworkBase
{
    public class Startup
    {

        private readonly string clientId = ConfigurationManager.AppSettings["okta:ClientId"];
        private readonly string redirectUri = ConfigurationManager.AppSettings["okta:RedirectUri"];
        private readonly string authority =ConfigurationManager.AppSettings["okta:OrgUri"];
        private readonly string clientSecret = ConfigurationManager.AppSettings["okta:ClientSecret"];

        public void Configuration(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions());

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
            {
                ClientId = clientId,
                ClientSecret = clientSecret,
                Authority = authority,
                RedirectUri = redirectUri,
                ResponseType = OpenIdConnectResponseType.CodeIdToken,
                Scope = OpenIdConnectScope.OpenIdProfile,
                TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = "name"
                },

                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthorizationCodeReceived = async n =>
                    {
                        // Exchange code for access and ID tokens
                        var tokenClient = new TokenClient(authority + "/v1/token", clientId, clientSecret);
                        var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, redirectUri);

                        if (tokenResponse.IsError)
                        {
                            throw new Exception(tokenResponse.Error);
                        }

                        var userInfoClient = new UserInfoClient(authority + "/v1/userinfo");
                        var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
                        var claims = new List<Claim>();
                        claims.AddRange(userInfoResponse.Claims);
                        claims.Add(new Claim("id_token", tokenResponse.IdentityToken));
                        claims.Add(new Claim("access_token", tokenResponse.AccessToken));

                        if (!string.IsNullOrEmpty(tokenResponse.RefreshToken))
                        {
                            claims.Add(new Claim("refresh_token", tokenResponse.RefreshToken));
                        }

                        n.AuthenticationTicket.Identity.AddClaims(claims);

                        return;
                    },

                    RedirectToIdentityProvider = n =>
                    {
                        // If signing out, add the id_token_hint
                        if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
                        {
                            var idTokenClaim = n.OwinContext.Authentication.User.FindFirst("id_token");

                            if (idTokenClaim != null)
                            {
                                n.ProtocolMessage.IdTokenHint = idTokenClaim.Value;
                            }

                        }

                        return Task.CompletedTask;
                    }
                },
            });
        }
    }
}
khellang commented 6 years ago

What have you tried? How do you know it's not working? Have you stepped through the code? You need to give us a bit more to work with here...

OHAVM commented 6 years ago

Hello khellang. My apologies for the lack of details. I have stepped through the following code:

 public class BaseModule : NancyModule
    {
        public BaseModule()
        {

            SetupLogger();

            //  Before allowing Request to go through, first check and see if end-user is Authentication
            Before += ctx =>
            {
                return AuthenticateUser();
            };
}

and found no authentication is occurring in the following:


        private Response AuthenticateUser()
        {
            var user = HttpContext.Current.User.Identity;
            //  Check is user authenticated via AD
            if (!user.IsAuthenticated)
            {
                //  If not, see if the JWT is available
                HttpRequest currentRequest = HttpContext.Current.Request;
                if (currentRequest.Cookies.Get(OKTA_COOKIE_SESSION_NAME) != null)
                {
                    string sessionValue = currentRequest.Cookies.Get(OKTA_COOKIE_SESSION_NAME).Value;

                    //  TODO: Authenticate JWT Token
                    return null;
                }
                else
                {
                    //  If user does not have JWT, return access denied
                    return NotifyEndUserAccessDenied(false);
                }

            }
            else
            {
                Logger.WriteLine("User has been proper authenticated as " + user.Name + " from the HTTP Address " + Request.UserHostAddress);
                return null;
            }
        }

I am probably missing something very simple. My apologies if this is the case.

OHAVM commented 6 years ago

Hello,

I see from numerous articles that I should be able to use the method "app.UseNancy()" in the Startup.cs and that this should smooth out integration. I'm receiving the following error when I attempt to:

Severity Code Description Project File Line Suppression State Error CS1929 'IAppBuilder' does not contain a definition for 'UseNancy' and the best extension method overload 'DelegateExtensions.UseNancy(Action<Func<Func<IDictionary<string, object>, Task>, Func<IDictionary<string, object>, Task>>>, NancyOptions)' requires a receiver of type 'Action<Func<Func<IDictionary<string, object>, Task>, Func<IDictionary<string, object>, Task>>>' ServiceFrameworkBase ~\RESTServices\BaseFramework\WebApplication1\Startup.cs 32 Active

Has anyone ever encountered this before and know of a solution?

khellang commented 6 years ago

Have you installed Nancy.Owin?