OIDC - Obtaining an Identity Token for use by a backend (no actual user) service running scheduled jobs
Asked Answered
G

2

5

We have an API (Actually several micro-services) secured by OIDC. The authorization server is owned and managed by our customers (not internal to us) and provides our SPA with an identity token. That SPA then passes that identity token to our backend server which validates the token, extracts the SubjectId (user), and then looks up their roles in an internal database. We are not using the token for authorization (meaning we ignore the claims), it is for authentication only.

We have a backend Windows Service that runs in a secure environment (so it can safely store secrets) that needs to call that same API. In order to call the API, it needs an OIDC Identity token to provide authentication. What is the best way to do that securely?

Diagram of the System

We have looked at these options:

  1. Username/Password Flow (OIDC) - We rejected this as it is deprecated. That makes it not a solid option for new code, but also, we can't be sure that our customers will allow its use now and in the future.
  2. Client Credentials Flow (OIDC) - We tried this... but it only provides an access token without identity token. Our whole requirement is an Identity token (As we use it to find the roles in our system)... so this doesn't seem to be an option.
  3. I looked at this article: https://nordicapis.com/how-to-handle-batch-processing-with-oauth-2-0/ which was interesting. I could set up a non expiring (or really long) identity token limited to one flow or the other... but requires me to host and OIDC server or manage theirs. Our customers won't give access.
  4. Martin Fowler wrote an article on using Refresh Tokens for this purpose: https://martinfowler.com/articles/command-line-google.html Two issues with this: It is for Auth not OIDC... so it doesn't deal with identity token. Should work, but curious if it has been done. Also, in my case, there is no person to refresh the refresh token when it expires... I need something that either doesn't expire or can be refreshed automatically by the backend services.

What is the best option?

Galagalactagogue answered 31/12, 2020 at 8:58 Comment(2)
Just to make sure I understand... You own all of the resources (SPA and API), but not the authorization server? And you've written the API to require OIDC rather than OAuth?Rapturous
#Andrew That is correct. Our software is deployed in customer environments. They wish to manage authentication (but not authorization) using their internal SSO (oidc). We accept the identity token to identify the user's, so we need oidc (which provides identity) not oauth (which manages access but not identity)Galagalactagogue
D
6

Let me start by saying, I wouldn't recommend the architecture you have. So, I'll give you two answers: a short-term practical one and a medium-term one that will result in a more ideal system.

First, the quick fix: turn on basic auth in the API. Then, call your API from the scheduled task runner using basic auth and a long secret, like this:

Authorization: basic bXktZ29vZC1zZXJ2aWNlOkY2M0JEOTkyLTMzNTUtNDYzNi1CNzFBLTM3RDg0QzI3ODEzRQo=

Then, keep validating the ID token you get from the SPA. I am assuming you're sending this to the API using the technique described in RFC 6750:

Authorization: bearer eyJhbGciOiJSUzI1NiJ9.ey...

So, these two different authentication methods make it easy for the API to tell who's calling it. What's more: it should be easy to implement, even with the authorization changes. This should hopefully allow you to put out the fire and then fix things on a more fundamental level.

When you get to that point, I would suggest that you do two things:

  1. Add in your own token service (i.e., an authorization server). An API should only ever trust its own authorization server, not some foreign one. This will form a pivot point, which is what you need now and don't have.

  2. Only accept access tokens in your API. APIs should only consume access tokens. If an API accepts an ID token, there's a 99% chance it's doing things wrong.

The SPA is the only one in your picture that needs an ID token. It should get that and an access token. What's more, it should get this from your OpenID Connect Provider (OP), not your customer's. This should do OpenID Connect to your customer, in turn, but will handle authentication, in general, so that your app can cope with other authentication requirements in the future without changes to the SPA. Also, other apps can reuse all this.

The SPA (and the scheduling service) should send access tokens to the API. The token (or a phantom or split form of it) should contain at least the subject ID, so it can obtain more info needed to authorize the call. This should come from your token service (i.e., your new OP since it will do OAuth and OIDC).

I would recommend the following high-level architectural documents and videos. These will help as you continue evolving your implementation:

Dolt answered 1/1, 2021 at 16:14 Comment(5)
Thank you for your thorough response. I enjoyed the links at the end as well, and found them informative. As I understand what your wrote, the two issues you see with my current design are: 1) I should be using Access Tokens, not Identity tokens to call my API and 2) The OAuth server is not part of my system, it is an external server. I should be adding another OAuth server to mint access tokens to provide access to the APIGalagalactagogue
That leads to 2 questions (if you don't mind): Our authorization logic cannot be described using Yes or No terms... it is relative to the user and his internal claims (not known to the OAuth server). Meaning, a person's position in the company, or what product they work on will limit what they can see. How do I pass that identity to the API? An Access token is good, but doesn't hold enough information for meGalagalactagogue
Second question: This entire system is hosted on-premises for a single customer per site. The OAuth server belongs to that customer and controls access to many applications. Once it has passed identity information to my API for login, what advantage is there to converting that into another token? Why not just use the Access token or Identity token from their internal OAuth server? Is it about allowing my API to control the Access tokens and specialize them?Galagalactagogue
Yes, you've understood my "issues" with your design correctly. Q1: Many peoples' APIs have complex authorization logic like you. Most don't realize though that OAuth access tokens can have not only a scope of access but also claims. From your API, you can look up additional claims too if you need. So, doing complex authorization logic in your API with an access token should be very doable. Look into this if it's not clear: curity.io/resources/architect/claims/scopes-vs-claims Q2: to avoid spaghetti trust youtu.be/pua07chpYBU?t=872Dolt
#Travis Thank you for your input.Galagalactagogue
X
0

Not really sure about your use-case, but of top of my mind I would probably do something like this instead:

enter image description here

The the SPA not deal with tokens at all, instead only give it a session cookie. Makes everything much more secure.

Let the service call the OIDC server to get its own access token, that can contain the necessary sub or role claims that it needs to call the API.

Xylotomous answered 31/12, 2020 at 11:5 Comment(8)
Thank you for your response. My issue is that I am not using OAuth/OIDC to manage the role claims. This is a customer requirement, so it is not negotiable. Instead OIDC is only providing Authentication as an Identity Token and the roles are managed in the API itself. I can't use Client Credential flow because it doesn't return an identity token and the access token it returns can't call the UserInfo endpointGalagalactagogue
To get the ID-token you need to manually (as a user login and give consent). so the only option in my picture is for the background service to ask the client service for the ID-token to use.Xylotomous
Interesting... but how would that work. If the backend service calls the "Client Service" to get the identity token, how would the Client Service authenticate the backend service in order to know what user to get a token for? Also, how would the Client service get that token without a live user to provide authentication and consent?Galagalactagogue
The user needs to manually login once and then it gets an ID and access token. The ID-token only have a 5 minute default lifetime, so its mainly used to create the local session with the SPA. The access token can live forever with our without using refresh tokens to renew them. Since the clients is a private client (Can keep secrets), there's nothing that stops you from creating a local internal API that the service can use to get a copy of the tokens. Or the client writes the token to a local disk....Xylotomous
Got it... To be honest, if that is the approach, I can simplify it a bit by giving the Service a token not from the OIDC server and having the API accept two types of authentication: OIDC and API minted tokens. However, this is not such a unique use case, and I would assume OIDC has a way of handling it more elegantly than building another auth mechanism for the APIGalagalactagogue
Its just one suggestion, I don't fully get your use-case but this is one approach. But the importance is that for the code-flow , a user must be involved, and you use the client credential flow for all the other cases.Xylotomous
Then depending on the OIDC service, there's also the option that the client can ask the IdentityServer for a new access token, specific for the API that the service can use, by using a token exchange, see docs.duendesoftware.com/identityserver/v5/advanced/… That is perhaps a even more clean solution.Xylotomous
Also, do be aware that the ID-token is not intended to be passed around to other system, its only used to create the "session", that's why it has a short lifetime.Xylotomous

© 2022 - 2024 — McMap. All rights reserved.