0

I am currently implementing an ASP.NET Core app with Angular as described here. With the help I got in this question, I was already able to get the application working locally and also on an IIS server in our test environment.

In this application, I have to communicate with another REST API which supports the following authentication types:

  • Kerberos
  • Basic
  • Bearer
  • Anonymous

For every request I perform against this API, I need to be authenticated as the same user which authenticated against my application (backend) via windows authentication. The reason for this is that all permissions regarding that API are connected to specific windows users or groups, so the API must know which user is performing the request so it only returns data which the user has access to.

Right now, I am a little bit confused about how to make this work because I never had to implement something similar before.

Which authentication method of the API do I have to use and what else do I have to consider/configure in order to forward the credentials/identity of the user to the API?

Chris
  • 1,417
  • 4
  • 21
  • 53
  • @Kevin What makes it too broad and opinion based? I am not asking "what is the best way to do this?", I just want to know which possibilities there are to make this work. From my understanding it is not possible with NTLM to have "hops" like this where the backend authenticates as the same user that authenticated against the backend itself to another API, but I simply don't know the true answer because I never had to do this before. – Chris May 06 '22 at 13:41
  • For Kerberos this is rather complicated, as you need permission to delegate, which is a big can of worms. Look for "constrained delegation". I have no idea how it applies to ASP.NET Core specifically if you're using it standalone, as most approaches go through IIS. If it is at all possible to avoid doing such things, it's generally worth avoiding (for example, allowing you to pass the user identity to the REST service as-is, with an "I am an internal application and you can trust what I'm saying" token). – Jeroen Mostert May 13 '22 at 10:28
  • What happens if you set [`UseDefaultCredentials`](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.usedefaultcredentials?view=net-6.0) to `true` on your `HttpClient`? It should pass your windows user credentials to the downstream API. – weichch May 13 '22 at 10:39
  • @weichch I am currently trying this inside a `WindowsIdentity.RunImpersonated(user.AccessToken, () => { ... }` block where `user` is `(WindowsIdentity)User.Identity` but I keep getting a HTTP 500 Error from the server hosting the REST API. Without doing this inside `WindowsIdentity.RunImpersonated...`, it will just pass the credentials of the user executing the backend, which is not what I want. Am I doing something wrong, or how can I make this work? I am a little bit confused about the whole Kerberos thing because I don't understand how thats related to what I am currently trying. – Chris May 13 '22 at 13:52
  • @weichch I just managed to figure out where the REST API is writing its logs. On the server side I can find the following errors: 1. `Request executing as anonymous (no valid identity associated with the request).`, 2. `{"Message":"Authorization has been denied for this request."}`, 3. `Request executing as NT AUTHORITY\ANONYMOUS LOGON` and 4. `Error occurred during writing output stream: 'The Windows identity 'NT AUTHORITY\ANONYMOUS LOGON' associated with the request cannot be impersonated.'` – Chris May 13 '22 at 14:42
  • 1
    Maybe try valid the request sent in the impersonation block has the right credentials? – weichch May 13 '22 at 23:40

1 Answers1

3

In order to come up with the best solution to your problem, first, you need to understand how Kerberos works at a conceptual level. A ticket by the caller (ex. user's browser) is obtained for a specific counterparty (service) by asking Active Directory KDC for a service ticket. This is done by presenting something called TGT ticket (think session / refresh token) to the KDC to prove caller's identity. TGT is obtained once when the app starts (and refreshed when closed to expiration). The KDC server will return a service ticket which contains the identity of the caller (user) and is encrypted with the password of the service. This means that anyone in possession of the credentials of the service to which the ticket is to be sent can decrypt the ticket and extract the user's identity. There's no communication to the Active Directory on the receiving side.

If you have a multi-hop service interaction such as yours where you need to propagate identity downstream, Kerberos has explicit support for this via a feature called delegation. Delegation allows the middle service to obtain new tickets as if they were the original user. There are few flavors to this feature:

Unconstrained delegation:

Unconstrained delegation is the oldest and most insecure method of propagating identity. Fundamentally it involves including a TGT ticket into the payload sent to the intermediatory service. Since anyone in possession of TGT ticket can be used to obtain new tickets to which it belongs, it allows intermediatory service to obtain tickets from KDC as if they were the original caller. For the purposes let's pretend Bob is the original user, Alex is a middle service, and Mike is the backend service where request actually ends up. The interaction works like this:

Unconstrained Delegation

  1. Bob: Hey KDC, I need a ticket to prove my identity to Alex. Here’s my Ticket Granting Ticket (TGT) to prove that I’m Bob
  2. KDC: Ok Bob, here’s your ticket to show to Alex that proves your identity. Since Alex is a trusted guy, I’m also including your TGT into the ticket so he can get stuff on your behalf by pretending to be you
  3. Bob: Hey Alex, I’m Bob, KDC told me to show you this ticket - you should be able to open it. And while you’re at it, fetch me a beer
  4. Alex (Bob): Hey KDC, I’m Bob and because I got this TGT, give me a ticket to prove my identity to Mike
  5. KDC: Ok Bob, here’s the ticket to show to Mike
  6. Alex (Bob): Hey Mike, get me a beer because I’m Bob, here’s a ticket from KDC proving that I’m Bob
  7. Mike: Hey Bob, you’re allowed to get beer, here you go.

This method has a serious security risk in that if the ticket sent by Bob to Alex is ever intercepted and "cracked", the attacker can use it to impersonate Bob to anyone on the network. If Bob is an AD admin and called a website that has a security flaw that allowed that ticket to be intercepted, there's a real risk that the attacker has obtained rights to do anything on your AD. Furthermore, since Kerberos tickets are derivatives of a user's password, a brute force attack can potentially expose the original password.

Constrained delegation

Constrained delegation improves significantly on security flaws of the unconstrained delegation. The way it works is we put a special flag on the intermediatory account (Alex) that allows him to obtain tickets from KDC to preconfigured list of counterparties. For example in the image below, Alex is allowed to impersonate Bob to Mike and nobody else. He's not even required to have any interaction with Bob - he can do it entirely on his own behalf. The interaction works as follows:

Constrained delegation

  1. Bob: Hey KDC, I need a ticket to prove my identity to Alex. Here’s my Ticket Granting Ticket (TGT) to prove that I’m Bob
  2. KDC: Ok Bob, here’s your ticket to show to Alex that proves your identity. 
  3. Bob: Hey Alex, I’m Bob, KDC told me to show you this ticket - you should be able to open it. And while you’re at it, fetch me a beer
  4. Alex: Hey KDC, I’m Alex (here’s my TGT), I want to be issued a ticket as if I’m Bob to prove my identity to Mike
  5. KDC: Ok Alex, I trust you to impersonate others when talking to Mike, here’s the ticket that will let you pretend that you’re Bob to show to Mike
  6. Alex (Bob): Hey Mike, get me a beer because I’m Bob, here’s a ticket from KDC proving that I’m Bob
  7. Mike: Hey Bob, you’re allowed to get beer, here you go.

Coming back to your specific scenario, you could go with the Kerberos delegation route. This will require some special configuration in active directory to setup - there's plenty of articles on the internet on how to configure each type of delegation.

But let's explore another option. You mentioned that downstream service supports Bearer (I assume JWT). Who's the the signer of these tickets? Because bearer tickets are often not scoped to specific audience (receiving party), anyone in possession of one can act as the original caller. This means that if you make your app obtain a JWT from an issuer that the downstream service trusts, you can just forward it to downstream without any special configuration. I'm going to assume your authentication provider is ADFS. Now you can configure ADFS to issue your app a JWT token that contains all the claims about the user. Assuming the JWT token it has the necessary claims to satisfy security requirements of both your app and the downstream app, the interaction would look as following:

  1. User tries to access a secure page on your app
  2. Your app redirects them to ADFS to obtain a JWT ticket.
  3. The user logs into ADFS (which may happen automatically if ADFS itself is configured to use Kerberos)
  4. The user is redirected back to your app and your app obtains JWT ticket via back channel
  5. You forward this JWT ticket to the trusted party as part of your service request (add HTTP header "Authorize: Bearer ").

One other thing that may be useful to you. I have a project that is able to convert Kerberos tickets into JWT and backwards. It acts like a proxy for your app (or a sidecar), manipulating the requests as they pass through. It may be helpful for your specific scenario. One advantage is that you're not tied to Windows with this: https://github.com/nmica/nmica.security

Andrew Stakhov
  • 1,105
  • 8
  • 26
  • Thanks a lot for the detailed response! I've actually never used this API with Bearer authentication and it's also disabled by default so I'm not 100% sure if it is a valid option in this situation. Here is a link to the documentation of the API which describes all the available authentication methods: https://docs.osisoft.com/bundle/pi-web-api/page/authentication-methods.html Do you think that this is actually a valid option in my scenario, or do I have to go with Kerberos delegation? – Chris May 16 '22 at 08:14
  • According to this link bearer is fully configurable. If you don't already have adfs set up, I would recommend using nmica project. https://docs.osisoft.com/bundle/pi-web-api/page/bearer-authentication-settings.html – Andrew Stakhov May 16 '22 at 10:07
  • Another option you can try is to run both middle service and this backend service on the same service account. Since they both share same password, you can just forward the incoming kerberos ticket downstream and reciever will be able to decrypt it. This will let you avoid delegation. To forward it it, just clone Authorize header into downstream request. It's not ideal as running two services on same service account might raise eyebrows in your org – Andrew Stakhov May 16 '22 at 10:23
  • What do you mean when saying "middle service" and "backend service"? Aren't those the same in this instance? I also don't understand your use of the word "downstream" and I'm not sure how your suggestion would work in this situation. Are you suggesting to run both the backend of the web application and the PI Web API itself under the same user? – Chris May 16 '22 at 13:50
  • Yes, that's what I'm proposing as that's the simplest solution for you. – Andrew Stakhov May 16 '22 at 14:14
  • I will look into this but I'm afraid that this might not be an option due to rather strict security policies. I'll have to clarify this with our admin. – Chris May 17 '22 at 06:31
  • I think I found yet another problem, but I'm not sure about this. When I inspect my current `WindowsIdentity` in my web application, it has "NTLM" specified as the authentiication type. When I then inspect the Security event logs on the server where the PI Web API is running, I can see that a logon attempt has been made via NTLM for an anonymous user. Doesn't this mean that I somehow have to force my web application to also use Kerberos internally instead of NTLM, as NTLM (afaik) does not support delegation? I can't seem to find an option anywhere which allows me to configure this :( – Chris May 17 '22 at 07:21
  • You need to have spn assigned to your app to make kerberos work. Your admin needs to create it for account under which it runs. – Andrew Stakhov May 17 '22 at 10:16
  • Do you know how this SPN has to look like or where I can find more information to know how to do this? Right now I can see a successful Kerberos login in the Security event logs but the web server (IIS) where I host my web application on returns a 401 error. In the IIS logs I can find an error 2148074248 but no further information anywhere about what's the problem so I'm not sure what to try next. – Chris May 17 '22 at 10:21
  • For web applications it takes the form of "http/mywebsite.com". The address is just the full host name (no path). – Andrew Stakhov May 17 '22 at 11:40
  • A SPN for "http/hostname" where hostname is the full host name of the server hosting the web application was already added, but this doesn't seem to fix the problem I posted above. – Chris May 17 '22 at 11:53
  • Are you forcing ntlm? I think that error code is associated with ntlm handshake. The fact that you're saying you got a successful kerberos authentication makes me think that a valid ticket is being sent across the wire but web server is trying to fall back to ntlm for whatever reason. Unfortunately idk much about that specific error scenario. If you get stuck trying to get kerberos to work, try NMica.Security I mentioned. It does not rely on OS for kerberos and can make things easier to diagnose (and you can run on Linux). – Andrew Stakhov May 17 '22 at 12:33
  • Btw if you're behind load balancer, ntlm gets finicky because it sends multiple requests back and forth and they have to arrive at same server to succeed. If your lb doesn't understand ntlm, it will send subsequent requests to wrong server which is missing part of the handshake, which would explain that error – Andrew Stakhov May 17 '22 at 12:35
  • I am not behind a load balancer at all so I don't think that this explains the problems I am experiencing. – Chris May 17 '22 at 12:49
  • After a lot of trial & error, debugging and many sessions with our admin, I'd like to stop looking into the Kerberos approach and try your suggestion regarding the use of Bearer authentication instead. Do you have any additional information, helpful links etc. so I can look further into this? I've never had to setup something similar before so I'm not quite sure what exactly has to be done to make this work. – Chris May 19 '22 at 09:32
  • The project is fairly well documented, I suggest you try it first and open up github issue if you need further help. I recommend you start with the utility project which is a cli app, and work on getting to obtaining kerberos ticket / validating ticket. Once you have that down, you can try launching the actual project, configuring it against your active directory and using postman to simulate traffic with kerberos tickets attached (which you can get via the kerbutil project i mentioned). Write a simple app to put behind the proxy, you should be able to see it receive jwt token – Andrew Stakhov May 19 '22 at 17:57
  • Are you talking about the NMica project, or which project do you mean? I do not really understand why exactly I would need that project and I would prefer going with standard / popular frameworks and technologies if possible. Platform independence isn't something that I am interested in, either. – Chris May 24 '22 at 08:32
  • Sorry, I do not have more info on how to diagnose the specific issue you're encountering. Kerberos is often a bit of a black box and error messages are very unhelpful, so it's hard for me to diagnose your specific usecase. – Andrew Stakhov May 26 '22 at 00:46