How to get the authenticated user name in Python when fronting it with IIS HTTP PlatformHandler and using Windows auth?
Asked Answered
T

3

2

HttpPlatformHandler supports forwarding the auth token by enabling the forwardWindowsAuthToken setting in the web.config. This sounds like a useful feature when needing to use Windows Integrated Authentication. The document on this is very vague and does not go into explaining how one could use this token to get the authenticated user name.

If this setting is set to true, the token will be forwarded to the child process listening on %HTTP_PLATFORM_PORT% as a header 'X-IIS-WindowsAuthToken' per request. It is the responsibility of that process to call CloseHandle on this token per request. The default value is false.

In my use-case, I needed to use Windows Integrated Authentication with Python, so did a setup with IIS fronting and using HTTP Platform Handler forward requests to Python.

The question is, how do I get the user name from the provided token in Python ? The token in the 'X-IIS-WindowsAuthToken' header seems like a 3 char hex like 22b.

Townswoman answered 9/7, 2020 at 2:8 Comment(0)
T
4

Okay, so I've researched this a bit and ended up reviewing how Microsoft.AspNetCore.Server.IISIntegrateion.AuthenticationHandler did it.

Then after figuring out one way, I wanted to post this answer so 1) I can find it later, 2) at least it's up on SO in case anyone else is wondering.

Okay, so the hex value is the handle and with the handle we can call impersonate user then get username, done.

All you need is the pywin32 package:

pip install pywin32

Complete example in Python:

import win32api
import win32security
if 'x-iis-windowsauthtoken' in request.headers.keys():
    handle_str = request.headers['x-iis-windowsauthtoken']
    handle = int(handle_str, 16) # need to convert from Hex / base 16
    win32security.ImpersonateLoggedOnUser(handle)
    user = win32api.GetUserName()
    win32security.RevertToSelf() # undo impersonation
    win32api.CloseHandle(handle) # don't leak resources, need to close the handle!
    print(f"user name: {user}")
    
    
Townswoman answered 9/7, 2020 at 2:8 Comment(1)
I use pipenv to install pywin32 and other packages and with that it just works fine on multiple servers, not sure why does it fail for you.Townswoman
B
2

@NakagawaMakoto has a good point, you shouldn't impersonate the user unless your app needs to by design. After some digging through archaic windows documentation and a bit of testing, here's an updated snippet that does the trick:

import win32api
import win32security
from flask import request
import logging

# Note if using aspNetCore instead of original httpPlatformHandler then the header would be 'Ms-Aspnetcore-Winauthtoken' instead of 'X-IIS-WindowsAuthToken'
# Also note that the header lookup is case-insensitive (for Flask at least, as in this example)
token_handle_str = request.headers.get('X-IIS-WindowsAuthToken', None)
if token_handle_str:
    token_handle = int(token_handle_str, 16) # need to convert from Hex / base 16
    sid = win32security.GetTokenInformation(token_handle, 1)[0] # TOKEN_INFORMATION_CLASS enum value 1 = TokenUser
    user, domain, account_type = win32security.LookupAccountSid(None, sid)
    win32api.CloseHandle(token_handle) # don't leak resources, need to close the handle!
    logging.warning(f'Request initiated by user {user}')
Bistoury answered 19/7, 2023 at 17:46 Comment(0)
N
0

This post has been very useful for me. One thing I really do not know whether impersonating to get a user's information is the right way. I am not a windows expert, though.

So I tried another path with GetTokenInformation and LookupAccountSid. I used ruby(ruby on rails) this time which I am more familiar with.

This is my first time to utilize fiddle, but I want to say that fiddle is not that difficult if you have slight knowledge of C.

I hope this post will help someone working with ruby on windows platform.

require "fiddle/import"
require 'fiddle/types'

module WIN32Security
  extend Fiddle::Importer
  dlload 'advapi32.dll'
  include Fiddle::Win32Types

  extern 'BOOL GetTokenInformation(HANDLE, UINT, PVOID, DWORD, PDWORD)'
  extern 'BOOL LookupAccountSidW(LPSTR, PVOID, LPSTR, PDWORD, LPSTR, PDWORD, PVOID)'

  # c.f. https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-token_information_class
  TokenUser = 1
end

module WIN32
  extend Fiddle::Importer
  dlload 'kernel32.dll'
  include Fiddle::Win32Types
  extern 'BOOL CloseHandle(HANDLE)'
end

module SelfServicesHelper
  def authorize_by_authtoken
    if request.headers.key? "HTTP_X_IIS_WINDOWSAUTHTOKEN"
      handle = request.headers["HTTP_X_IIS_WINDOWSAUTHTOKEN"].hex

      buflen = 64
      tokenInfo, len = "\0" * buflen, [0].pack("L!")
      if WIN32Security::GetTokenInformation(handle, WIN32Security::TokenUser, tokenInfo, buflen, len) != 0
        namelen, dnamelen, use = *[32,32].map{|e| [e].pack("L!")}, [0].pack("I")
        namebuf, dnamebuf = [namelen, dnamelen].map{|e| "\0".encode("utf-16le") * e.unpack1("L!")}
        # ... PSID is at the top of tokenInfo
        if WIN32Security::LookupAccountSidW(nil, tokenInfo.unpack1("Q!"), namebuf, namelen, dnamebuf, dnamelen, use) != 0
          namelen, dnamelen = [namelen, dnamelen].map{|e| e.unpack1("L!")}
          WIN32::CloseHandle(handle)
          logger.debug {"namebuf: %s, dnamebuf: %s" % [namebuf[0, namelen].encode("utf-8"), dnamebuf[0, dnamelen].encode("utf-8")}
        else
          logger.error "LookupAccountSidW returned false, last error: %d" % Fiddle.win32_last_error
        end
      else
        logger.error "GetTokenInformation returned false, last error: %d" % Fiddle.win32_last_error
      end
    else
      logger.debug "no HTTP_X_IIS_WINDOWSAUTHTOKEN"
    end
  end
end
Nastassia answered 14/5, 2023 at 21:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.