antiduh / nsspi

A C# / .Net interface to the Win32 SSPI authentication API
BSD 2-Clause "Simplified" License
64 stars 34 forks source link

ServerContext not Stateless? #29

Open pvaartstra opened 1 month ago

pvaartstra commented 1 month ago

I created a minimal WebAPI application, attempting to do Negotiate authentication using the NSSPI library. The first negotiate token I get back from the client (browser or PowerShell) generates a ContinueNeeded status from serverContext.AcceptToken(). I send the generated server token back to the client, but then the next negotiate token I get back from the client throws an exception within serverContext.AcceptToken().

Here is the message flow: (client:) GET /secure (server:) HTTP 401 WWW-Authenticate: Negotiate (client:) GET /secure Authorization: Negotiate {initial token generated by client} (server:) HTTP 401 WWW-Authenticate: Negotiate: {server token generated by server.AcceptToken} (client:) GET /secure Authorization: Negotiate {presumably a secondary token generated by the client based on the server token} (server:) HTTP 500 Body: {"Failed to call AcceptSecurityContext. Error Code = '0x80090308' - \"The provided authentication token is invalid or corrupted.\"."}

I'm guessing this has to do with the multi-threaded nature of WebAPI and that the serverContext instance is destroyed between the first step and the second step of authentication. I turns out that if I make the serverContext static, that two-step negotiation works fine. Is SeverContext not stateless? How can I save and restore its state if I need to avoid static members?

antiduh commented 1 month ago

Hi, sorry but ServerContext is not stateless. When negotiating a connection, you're building a context between the server and a specific client, and it takes several messages to do. It's been a long time since I looked at this, but if I recall correctly, there are modes available to minimize the back-and-forth. Also, for what it's worth, NTLM is being deprecated by Microsoft, and soon. Your only option would be to use Kerberos.

pvaartstra commented 1 month ago

Given that it is not stateless, how can I save and restore state between HTTP requests? I do not think storing the entire context in application static memory is appropriate because WebAPI is multi-threaded.

antiduh commented 1 month ago

I'm not a web developer, so I don't know. You'll have to store it in a collection in some way that you can relate it back to each request; that's what cookies are for, right? You'll need to manage lifetime, so you don't hold onto the context indefinitely.

I would not be surprised if ASP has a better way to do exactly what you're trying to do (I thought ASP supported integrated auth natively?). I wrote this project for a case where I'm not doing http, it was for a case where communication happens over much more primitive systems.

Have you looked at NegotiateStream?

pvaartstra commented 1 month ago

Yes, cookies can be used to persist data between Http requests. But that brings me back to my question: what "data" about a ServerContext needs to be persisted, and how can it be read and restored?

You are correct that ASP has a better way to do this - at least for standard authentication. A couple of simple tags in web.config will generally take care of it. But what I am ultimately trying to do is build a Proxy. This requires Proxy Authentication which from what I can tell uses the same mechanics just different header names ("Proxy-Authenticate" and "Proxy-Authorization") rather than .

I assume standard authentication is more familiar though, so I hoped to write a simple prototype using Negotiate over standard authentication using SSPI and then modify it for Proxy authentication.

I took a look at NegotiateStream and it appears to assume a read/write connected socket. I don't see how it would work with the disconnected Request/Response model of HTTP.

antiduh commented 1 month ago

But that brings me back to my question: what "data" about a ServerContext needs to be persisted, and how can it be read and restored?

The SSPI API is an opaque API for having a conversation with a Windows security provider such as NTLM or Kerberos (or the Negotiate meta-package which, after a conversation, chooses for you which one your client is compatible with).

Initializing a security context between a client and a server in these protocols is a conversation, thus inherently stateful. All I'm doing is providing you a c# API to make easy to call the Windows SSPI API; if you were doing this yourself, you'd have the same experience. It's not about what ServerContext needs for itself; it's about what SSPI needs. Run the demo program and debug it, you'll see that SSPI asks us to send and receive multiple tokens before it's satisfied.

My SO answer here discusses some of the details (InitializeSecurityContext, AcceptSecurityContext):

https://stackoverflow.com/a/24312883/344638