marv
marv

Reputation: 211

How to issue access token based on Windows Authentication with Identity Server 4

My goal is to protect a Web API, such that it can only be accessed by a client using an access token issued by IS based on Windows authentication. I worked through this basic sample: http://docs.identityserver.io/en/release/quickstarts/1_client_credentials.html

Now, I need to extend the basic sample such that the access token returned to the client is issued based on Windows authentication. More specifically, I need to have the user (which is executing the client application) to be authenticated against Active Directory when requesting an access token. How should this be done?

I have already been running the quick start (https://github.com/IdentityServer/IdentityServer4.Templates) successfully, where the login is based on a Windows external provider, but I cannot figure out how to adopt this functionality to my strategy.

I tried using an Extension Grant (http://docs.identityserver.io/en/release/topics/extension_grants.html) and have the ValidateAsync() method be the one to do the authentication against AD, but could not make it work (primarily since HttpContext is not available). Is this even the correct approach?

Update

In this system, the client is a console application (without human interaction), thus the context is the account running the application. I have been running the QuickstartUI and see how the AccountController logic handles the "Windows" button, but cannot grasp how to combine this with requesting access tokens. My client code goes like this:

static async Task Main(string[] args)
{
  var disco = await DiscoveryClient.GetAsync("http://localhost:50010");

  var tokenClient = new TokenClient(disco.TokenEndpoint);
  var tokenResponse = await tokenClient.RequestCustomGrantAsync("CustomWindows"); // Not sure about this

  var client = new HttpClient();
  client.SetBearerToken(tokenResponse.AccessToken);

  var response = await client.GetAsync("http://localhost:50011/api/identity");
  var content = await response.Content.ReadAsStringAsync();
  Console.WriteLine(JArray.Parse(content));

  Console.ReadLine();
}

I am not sure how to use the TokenClient to get an access token in this case. I would prefer not to store and use passwords, but have IS issue access tokens based on authenciating the client context against AD. If implicit or hybrid flows must be used in this case, how must that be done?

Upvotes: 4

Views: 2671

Answers (1)

berhir
berhir

Reputation: 1450

I had the same requirement and implemented it using an extension grant.
This is the code of the extension grant:

public class WinAuthGrantValidator : IExtensionGrantValidator
{
    private readonly HttpContext httpContext;

    public string GrantType => WinAuthConstants.GrantType;

    public WinAuthGrantValidator(IHttpContextAccessor httpContextAccessor)
    {
        httpContext = httpContextAccessor.HttpContext;
    }

    public async Task ValidateAsync(ExtensionGrantValidationContext context)
    {
        // see if windows auth has already been requested and succeeded
        var result = await httpContext.AuthenticateAsync(WinAuthConstants.WindowsAuthenticationSchemeName);
        if (result?.Principal is WindowsPrincipal wp)
        {
            context.Result = new GrantValidationResult(wp.Identity.Name, GrantType, wp.Claims);
        }
        else
        {
            // trigger windows auth
            await httpContext.ChallengeAsync(WinAuthConstants.WindowsAuthenticationSchemeName);
            context.Result = new GrantValidationResult { IsError = false, Error = null, Subject = null };
        }
    }
}

And this is the client code:

var httpHandler = new HttpClientHandler
{
    UseDefaultCredentials = true,
};

// request token
var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret", httpHandler, AuthenticationStyle.PostValues);
var tokenResponse = await tokenClient.RequestCustomGrantAsync("windows_auth", "api1");

Upvotes: 0

Related Questions