共用方式為


Using a JWT on a Windows Service

One of this days I had this really fun challenge that I need to tackle.
So consider the follow, I had a solution where I need to authenticate a Windows Service (so non interactive) against an (ASP.net) Web API, using ADFS 3.0 with ADAL.NET, and the current user’s credentials, fun right?

In my case, after some simple tests using a console app I was able to successfully get tokens (JWT) using the AcquireTokenAsync overloads that display a login screen, which then automatically logins in as the current user and disappears without interaction.
That obviously isn't appropriate for a Windows Service though, because when I try this same approach (just for the kicks) in my Windows Service, it throws an error about displaying UI in non-interactive mode, duhh.

Since I was doing this just for the fun, I went through my ADFS servers Windows Log, and found the obvious errors:
Encountered error during federation passive request. Microsoft.IdentityServer.RequestFailedException: MSIS7065: There are no registered protocol handlers on path /adfs/oauth2/token to process the incoming request. at Microsoft.IdentityServer.Web.PassiveProtocolListener.OnGetContext(WrappedHttpListenerContext context) This is from a POST to /adfs/oauth2/token HTTP/1.1: resource=https%3A%2F%2Fadfs-server%2Fapi_name&client_id=983EB4F7-A4D1-4897-A2BD-5D6C268A5813&scope=opened
Which results in an HTML document saying an error has occurred.

Below is the code that I was trying to use:

var authenticationContext = new AuthenticationContext("https://adfs-server/adfs", validateAuthority: false); //validate authority can only be true with Azure AD var tokenResult = await authenticationContext.AcquireTokenAsync("https://adfs-server/api_name", "983EB4F7-A4D1-4897-A2BD-5D6C268A5813", new UserCredential()); //use current login -- this line fails request.Headers.Add("Authorization", tokenResult.CreateAuthorizationHeader());

Analysis

So lets start by clarifying some facts about the Pros and Cons of using ADFS in my troubleshoot scenario

  • ADFS only supports the full auth flow, which consists of
    • Win service browse to web app and be redirected to ADFS for authorization
    • Authenticate to ADFS and fetch an authorization code
    • Win service redirected back to the web app with the auth code
    • Web app to send the authorization code to ADFS along with an authorization request for a remote resource
    • ADFS to validate the auth request and auth code an emit an Auth token (or a refresh token)
    • ADFS to redirect the Win service back to the web app with the Auth token
    • The Web app to fetch the remote resource using the Auth token or the refresh one
  • All of the above works at least if 2 conditions hold
    • The Win service must be able to follow redirections
    • The win service must be able to authenticate to ADFS as the current logon user (using whatever mechanism ADFS supports, including Windows Auth)

In my case since I wanted to run in a non-interactive session, it couldn't rely on the user to login to ADFS, so it needs to use the credentials right away.
This can be done by following the “Resource Owner Password Credentials Grant”
Resource Owner Password Credentials Grant
Unfortunately, ADFS3 only supports the Authorization Code Grant, so If I wanted to do this I would need build my own Authorization Server OR switch to ADFS on Server 2016 OR use Azure AD.

However I was not fully convinced that the above options would be the only ones I had, so I fuss a little more around this and found some references on the forums saying that instead of using:
var authenticationContext = new AuthenticationContext(authority, validateAuthority: false); var tokenResult = authenticationContext.AcquireTokenAsync(resorceId, clientId, new UserCredential()).Result;

I should be using:
var tokenResult = authenticationContext.AcquireTokenAsync(resorceId, clientId, redirectUrl, new PlatformParameters(PromptBehavior.Auto)).Result;

Unfortunately I   do not think that is true, let me elaborate on this a little more.

So, lets assume I will use the below code:
var tokenResult = authenticationContext.AcquireTokenAsync(resorceId, clientId, new UserCredential()).Result;

This should use the resource owner credentials grant which is a flow in OAuth2 designed to grab a security token for a resource without an authorization code first.
No user interaction is needed since the credentials are sent. There’s only one step which is go to the /token endpoint and ask for a token.
This flow is not implemented in ADFS3. You need AAD or Server 2016 (THAT'S MY SOLUTION, THERE).
See below the flow:

In fact the error I was getting was due to the fact that the grant_type I was getting was invalid for ADFS 3.0 (the one that I was using).

So, now lets look at the second and suggested approach:
var tokenResult = authenticationContext.AcquireTokenAsync(resorceId, clientId, redirectUrl, new PlatformParameters(PromptBehavior.Auto)).Result;

To be honest, I actually never seen this overload passing on "platformparameters" and I cannot find it either. However, given its assuming passing the resourceId, the clientId and the redirectUrl I suppose this is equivalent to trigger an authorization grant flow.
So, this means my first will be a call to the /authorize endpoint on ADFS to get a token.
Then, once I have the code I send a POST against the /token endpoint with the authorization code obtained in step 1.
This requires user interaction as the client (application) is not aware of the credentials.
See, below image to a clear understanding of the flow:

Yes, all good here, but the flow requires two steps including a user-driven one (that will not work for me)

So in a nutshell, in my scenario, I found out that the best approach for me would be choosing between one of the below options:

  1. Upgrade my ADFS Server
  2. Use Azure AD
  3. Implement my own OAuth2 Authentication Server

 

Hope that helps