Many people ask frequently on the Microsoft Newsgroups how can
they verify that a username and password match against the SAM. Well, on NT, at
least, it turns out there are two different methods you can use:

  1. LogonUser(): It's easy to use, and gives you an
    access token you can use for impersonation later, but unfortunately,
    requires that the caller holds the SeTcb privilege, which poses a great
    security risk. The usual workaround is to create an authentication server
    that runs as a service under the SYSTEM account and does the real work of
    calling LogonUser().

  2. SSPI: This is the Security Service Provider
    Interface,
    which provides a set of APIs you can use to authenticate a
    user, and acquire credentials for it. The problem with using SSPI is that is
    complicated, and some aspects of it are not that well documented. To make
    matter worse, the SSPI samples on the KB and Platform SDK leave a lot to be
    desired. The nice thing about SSPI, on the other hand, is that it's
    transport-neutral, as you are responsible for transmitting the packets
    between client and server (which might be in the same process). Also, some
    providers support message encription.


So, how can you use SSPI, in a single process, to
authenticate a given username and password? Here it is step by step:

  1. Load security.dll and get a pointer to the function table.
  2. Say you want to use the NTLM package (which is the most common), so you call
    QuerySecurityPackageInfo() to obtain a SecPkgInfo pointer
    (don't forget to release it later using FreeContextBuffer()).

  3. Now, think of your app as having two sides in the authentication process:
    a client and a server. For the server, you'll need to call
    AcquireCredentialsHandle() once. For the client, you need to do that,
    too,
    with a minor change: since you want to get credentials for a different user,
    you need to fill in a SEC_WINNT_AUTH_IDENTITY struct and pass a pointer to
    it to AcquireCredentialsHandle() as the 5th parameter. Keep in mind that you
    should keep the struct around until your done.

  4. Start the client/server conversation by successive calls to
    InitializeSecurityContext (for the client side) followed by
    AcceptSecurityContext (for the server side), passing the
    output buffer of one as the input buffer to the other. Note that on the first call to ISC() you'll pass NULL
    as the input buffer.

  5. Finally, keep doing that until the client gets a return value of SEC_E_OK
    (assuming the auth went ok). It's important that you watch the client side,
    because when AcceptSecurityContext() returns SEC_E_OK, you still have to
    pass the client the buffer returned, so you're not really done yet.

  6. Impersonate: A call to ImpersonateSecurityContext() will cause you to
    impersonate the security context of the user you just authenticated. Keep in
    mind that you call this function with the Server's context handle, not the
    client's. After that, you can go back to the original security context with
    a call toRevertSecurityContext().

One important thing to notice while you are calling InitializeSecurityContext()
and AcceptSecurityContext() is that one of those returns SEC_I_COMPLETE_NEEDED
or SEC_I_COMPLETE_AND_CONTINUE, you need to call CompleteAuthToken().

If you are using SSPI in server/client applications, you should be aware of what I
consider a rather annoying problem with SSPI: The SSPI client is not notified of the
authentication results, and rather always returns SEC_E_OK (unless another
error pops up) in the last round, even if the server later denies the logon request. This means
you need to make your server trap the return of the last call to AcceptSecurityContext()
and explicitly tell your client if the logon request was accepted or not.

I've built a C++ library that greatly simplifies the use of SSPI, while
keeping it transport-independent, giving the programmer control of the buffers.
Included in the package are:

  1. Library Source Code
  2. Precompiled static libs for release ansi and unicode,
    but you can build the debug versions from the source code

  3. A simple test app
  4. Some documentation, although it's quite outdated, so don't trust it too much :)

Minor Comment: The library is very much oriented toward creating
authentication services built on top of NTLM or Kerb, so currently you won't be
able to (easily) support things like SCHANNEL. That's one of my goals for the
next version, but I really haven't had the time to sit down and start version
2.0

Get the file here: wsspi.zip

Update: WSSPI V2.0 development is pretty far ahead, so you should see it in a
couple of weeks. Currently, I'm testing the library implementation, and adding SCHANNEL support
to it. Here's some of the things you can expect:

Update 08/01/2001: There are finally two updates to WSSPI. The first one is courtesy of
Jim Johnson, which added support to the basic WSSPI 1.0 for message signing and verification. You can
get this version here.

Since I've been meaning to finish WSSPI 2.0 for so long, and have not posted anything, I decided to
post what's already built in case someone finds it useful. Note that this is a complete rewrite, from
scratch of WSSPI, called WSSPI 2.1, but it is unfinished. Mostly, the Kerberos support is broken.
You can get the file here