Giter Club home page Giter Club logo

pysco68.owin.authentication.ntlm's Introduction

Pysco68.Owin.Authentication.NTLM

A passive NTLM autentication middleware for OWIN. This middleware enables you to use NTLM authentication independently of IIS or HTTPListener. Additionally it integrates easily with ASP.NET Identity 2.0. Being a passive middleware, it will enable you to use local application accounts with Windows Authentication as yet anoter mean of authentication!

Installation

You can either clone this repository and include the project in your sources or install the nuget package using:

Install-Package Pysco68.Owin.Authentication.Ntlm 

Usage

After installing the package as a dependency in your project you can

using Pysco68.Owin.Authentication.Ntlm;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Owin;

public class Startup
{
	public void Configuration(IAppBuilder app)
	{
		// use default sign in with application cookies
		app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ApplicationCookie);

		app.UseCookieAuthentication(new CookieAuthenticationOptions()
		{
			AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie                
		});

		// Enable NTLM authentication
		app.UseNtlmAuthentication();

		// .....
	}
}

As with any other passive middleware you must provide some point of entry in your application that will start the authentication. As an example you could add a route like this one to your Accounts controller:

[AllowAnonymous]
[Route("ntlmlogin")]
[HttpGet]
public IHttpActionResult Ntlmlogin(string redirectUrl)
{
    // create a login challenge if there's no user logged in!
    if (this.User == null)
    {
        var ap = new AuthenticationProperties()
        {
            RedirectUri = redirectUrl
        };

        var context = this.Request.GetContext();
        context.Authentication.Challenge(ap, NtlmAuthenticationDefaults.AuthenticationType);
        return Unauthorized();
    }

    return Redirect(redirectUrl);
}

Note: That route/action would be the place to sign in with (or to create) a local application account too.

Please note that beause of the slightly unusual way NTLM works (from OWIN perspective) you have to take care that the CookieAuthentication middleware isn't applying redirects when this middleware returns a 401 during the first two steps of authentication.

app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
	AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie 
	LoginPath = new PathString("/api/account/ntlmlogin"),
	Provider = new CookieAuthenticationProvider()
	{
		OnApplyRedirect = ctx =>
		{
			if (!ctx.Request.IsNtlmAuthenticationCallback())    // <------
			{
				ctx.Response.Redirect(ctx.RedirectUri);
			}
		}
	}
});            

So make sure to check the above if you get strange redirects or redirect loops!

Note: you can provide a path to the IOwinRequest.IsNtlmAuthenticationCallback(PathString redirectPath) extension method. This is useful if the effective callback path is different from NtlmAuthenticationOptions.DefaultRedirectPath (for example if you specified something different in the setup or if you use the middleware in a virtual directory: see #7)

If you need to have detailled control about who logs into your application (say based on windows domain groups) you can pass a filter expression to the middleware:

// Enable NTLM authentication
app.UseNtlmAuthentication(new NtlmAuthenticationOptions() 
{
	Filter = (windowsIdentity, request) => 
		windowsIdentity.UserName.StartsWith("FOOBAR\\")	// user belongs to the domain "FOOBAR"
});        

Additionally you may want to controll the creation of the authentication identity being created when the user is authenticating. You can provide your own callback for OnCreateIdentity which takes the user's windows identity and the authentication options an must provide a ClaimsIdentity:

// Enable NTLM authentication
app.UseNtlmAuthentication(new NtlmAuthenticationOptions() 
{
	OnCreateIdentity  = (windowsIdentity, options, request) => 
	{
		// If the name is something like DOMAIN\username then
  		// grab the name part (and what if it looks like username@domain?)
        var parts = state.WindowsIdentity.Name.Split(new[] { '\\' }, 2);
        string shortName = parts.Length == 1 ? parts[0] : parts[parts.Length - 1];

        // we need to create a new identity using the sign in type that 
        // the cookie authentication is listening for
        var identity = new ClaimsIdentity(Options.SignInAsAuthenticationType);

        identity.AddClaims(new[]
        {
            new Claim(ClaimTypes.NameIdentifier, state.WindowsIdentity.User.Value, null,
                Options.AuthenticationType),
            new Claim(ClaimTypes.Name, shortName),
            new Claim(ClaimTypes.Sid, state.WindowsIdentity.User.Value)
        });                              


		return identity;
	}
});        

Kudos

Big thanks to Alexey Shytikov (@shytikov) and his Nancy.Authentication.Ntlm (https://github.com/toolchain/Nancy.Authentication.Ntlm) implementation of Ntlm for Nancy. It was a huge help!

Thanks to the contributors:

  • Brannon King (@BrannonKing) for the Filter callback
  • Martin Thwaites (@martinjt) for the OnCreateIdentity callback

Help / Contribution

If you found a bug, please create an issue. Want to contribute? Yes, please! Create a pull request!

pysco68.owin.authentication.ntlm's People

Contributors

brannonking avatar pysco68 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pysco68.owin.authentication.ntlm's Issues

The identity in RequestContext.Principal.Identity does not provide domain name

Implementing an ApiController I get a member this.RequestContext.
This provides the identity of the authorized user.

This identity, created based on ApplicationCookie (?), does not provide the domain name anymore, but only the user name.
I guess this is due to code in Pysco68.Owin.Authentication.Ntlm.NtlmAuthenticationHandler.AuthenticateCoreAsync. The identity is created and claims are added. For the ClaimTypes.Name only the short name is given. Instead the domain name (extracted before) shall be provided somehow, e.g. just as ClaimTypes.Name.

when to send challenge with SignalR?

Can you help me knowing how to use this Ntlm authentication with Owin-hosted SignalR? I need to know when to send the challenge. I also need to know if it should use cookies or authenticate every time. Also, I'm using the SignalR .NET client (not a web browser).

Authorization header always missing

Hello, thanks for releasing this cool project! I've spent a decent amount of time adding it into my own project, but I can't ever seem to get the point where it actually evaluates the NTLM challenge because Request.Headers["Authorization"] always returns null.

The only thing that I am doing is initiating the challenge much like an external login provider would (there's a "Windows" button on my login page), but otherwise everything else is pretty straight forward. Doing so should remove the need for me to create a separate "ntlmlogin" route, correct?

Here's a screenshot of what I'm working with.

ntlm-not-working

And here's everything related to this in my startup code:

app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ApplicationCookie);

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    AuthenticationMode = AuthenticationMode.Active,
    LoginPath = new PathString("/account/login"),
    Provider = new CookieAuthenticationProvider()
    {
        OnApplyRedirect = ctx =>
        {
            // do not show the login form for unauthorized api requests, ajax requests or ntlm challenges
            if (!ctx.Request.IsApiRequest() || !ctx.Request.IsAjaxRequest() || !ctx.Request.IsNtlmAuthenticationCallback())
            {
                ctx.Response.Redirect(ctx.RedirectUri);
            }
        },
        // Enables the application to validate the security stamp when the user logs in.
        // This is a security feature which is used when you change a password or add an external login to your account.  
        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<MyUserManager, User, int>(
            validateInterval: TimeSpan.FromMinutes(30),
            regenerateIdentityCallback: (manager, user) => IdentityHelpers.GenerateUserIdentityAsync(user, manager),
            getUserIdCallback: id => id.GetUserId<int>())
    }
});

app.UseNtlmAuthentication(new NtlmAuthenticationOptions
{
    AuthenticationType = string.Format("NTLM_profile-{0}", profileId),
    CallbackPath = new PathString(string.Format("/signin-ntlm-{0}", profileId)),
    Filter = (windowsIdentity, options) =>
    {
        if (string.IsNullOrWhiteSpace(profileAuth.Ntlm.DomainGroupNameFilter))
        {
            return true;
        }

        var strippedGroupFilter = profileAuth.Ntlm.DomainGroupNameFilter.Replace("\\", "");
        return windowsIdentity.Name.StartsWith(string.Format("{0}\\", strippedGroupFilter));
    },
    OnCreateIdentity = (windowsIdentity, options) =>
    {
        // If the name is something like DOMAIN\username then
        // grab the name part (and what if it looks like username@domain?)
        var parts = windowsIdentity.Name.Split(new[] { '\\' }, 2);
        string shortName = parts.Length == 1 ? parts[0] : parts[parts.Length - 1];

        var identity = new ClaimsIdentity(options.SignInAsAuthenticationType);
        identity.AddClaims(new []
        {
            new Claim(ClaimTypes.NameIdentifier, windowsIdentity.User.Value, null, options.AuthenticationType),
            new Claim(ClaimTypes.Name, shortName), 
            new Claim(ClaimTypes.Sid, windowsIdentity.User.Value)
        });

        return identity;
    }
});

I've modified the AuthenticationType and CallbackPaths because I am setting this up in a multi-tenant environment where each "profile" could enable or disable this feature.

Should I be setting the "Windows Authentication" project setting to "Enabled" for this? I doubt I'd need to do that since the Interop class does a lot of the heavy lifting...

Any assistance would be greatly appreciated!

Prompted for credentials

I'm having an issue where the first time the user is authenticated they are being prompted for credentials instead of the browser simply passing them along behind the scenes. I am fairly new to Owin in general and don't fully understand all of the concepts yet so I'm assuming this is probably something I'm missing.

NTLM authentication works well on IIS Express (Visual Studio) but fails on IIS.

While working well on the built-in Visual Studio's IIS, the authentication workflow is interrupted by the "Authentication Required" popup when running on the standard IIS. No matter adding the site's URL to the "Intranet" zone in the browser's settings, it is still ending up with the popup. I was wondering if there is something special about the IIS' configuration.

Feature request: give me a hook for rejecting particular users

I would like the ability to reject specific users according to my own criteria. In other words, the users are valid domain users, but I don't want to authenticate them. Can we get a callback for this? In addition to the Identity object, I would need enough information to determine if the request came from the local machine.

HandshakeState.IsClientResponseValid failed while accesing specific domain instead localhost

Hi,
I would like to ask about problem of running NTLM on specific domain in development.

  • I have local DEV machine in domain e.g. mydomain.cz
  • I have mydomain.cz and app.mydomain.cz in Trusted Sites in Windows Iternet Settings (I have localised win, so I hope name is correct).
  • I have following in etc/hosts
    127.0.0.1 app.mydomain.cz
  • Consider following console app using :
using Microsoft.Owin.Hosting;
using Pysco68.Owin.Authentication.Ntlm.Tests;
using System;
namespace NtlmTest
{
    class Program
    {

        private IDisposable Server;

        public void Init()
        {
// using Pysco68.Owin.Authentication.Ntlm.Tests.WebApplication with method Configuration changed to static
            this.Server = WebApp.Start("http://app.mydomain.cz:9999/", WebApplication.Configuration);
        }

        public void Teardown()
        {
            this.Server.Dispose();
        }

        static void Main(string[] args)
        {
            var p = new Program();
            p.Init();
            Console.ReadKey();
            p.Teardown();
        }
    }
}

runing this code I'm unable to login.
HandshakeState.IsClientResponseValid is false (caused by Interop.AcceptSecurityContext -> returns -2146893044).
If I run app on http://localhost:9999 everything is OK.

Can I ask you for advice? Google doesn't help for now :/

Kind Regards, Jan.

Virtual Directory CallbackPath

When I run this app under IIS Express (http://localhost:<port>) NTLM authentication works properly. The 302 response after creating the authentication challenge contains the proper NTLM authentication sign-in path in the Location header, and the browser includes the Authorization: NTLM <token> header when making the request to /authentication/ntlm-signin?state=<state_id>.

I am running into problems however when attempting to run the app from Local IIS 7.5 under a virtual directory like so: http://localhost/myapp/

In NtlmAuthenticationOptions I set

CallbackPath = "/myapp/authentication/ntlm-signin"

which is necessary to find the correct login path.

The problem seems to be that the Authorization: NTLM <token> header is missing from the request to /myapp/authentication/ntlm-signin, and so a response containing the WWW-Authenticate: NTLM header is returned. For some reason the browser is not including the Authorization token in its request to NTLM authenticate.

Am I possibly missing a step in the IIS configuration? Or maybe this could be related to the Double Hop Authentication issue?

possible to use token instead of cookie?

Is it possible to use the JWT token stuff instead of a cookie with this library? I don't understand where this package sets the cookie, either. Please help me understand how that works.

Sample using this Nuget package

Hi,

could you please provide complete sample as .sln file using this package for NTLM authentication, I tried my own but it was giving compile time error, actually I am new to OWIN authentication.

Thanks.

You should port to .net core

Does this work well with IdentityServer4 ? I'm looking for something like this that will work with IS4 and .net core.

IOwinRequest in OnCreateIdentity callback

Hi pysco68,

Is it possible to add IOwinRequest to OnCreateIdentity callback (like in Filter expression)?
I have a custom UserManager instance in request which I need to access when creating ClaimsIdentity.

Thank you!

Configuration of CallbackPath not working as expected

I've tried to use configuration for CallbackPath to get custom URL at all.
But the login fails, I get (nearly) endless redirect (seen in Fiddler).
I've created a new unit test according current code base.
Please help to either fix the code or fix the test.

[Test]
public async void ImplicitLogIn_CustomCallbackPath_Successful()
{
    var handler = new HttpClientHandler 
    { 
        AllowAutoRedirect = true, 
        Credentials = CredentialCache.DefaultNetworkCredentials
    };
    WebApplication.Options.CallbackPath = PathString.FromUriComponent("/My-SignIn");

    var client = new HttpClient(handler);            
    client.BaseAddress = this.BaseAddress;
            
    var response = await client.GetAsync("/api/test").ConfigureAwait(false);
    Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "Http status is incorrect");

    var result = await response.Content.ReadAsAsync<string>().ConfigureAwait(false);
    var currentUserName = Environment.UserName;
    Assert.AreEqual(currentUserName, result);            
}

Retrieve User Identity [Question]

How do we retrieve the user info(domain/username) with your solution ?

I tried overriding NtlmAuthenticationOptions.OnCreateIdentity but I'm never stepping in when debugging.

Btw I'm using Nancy with OWIN so it seem I dont have access to ApiController.User....

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.