keycloak / keycloak

Open Source Identity and Access Management For Modern Applications and Services
https://www.keycloak.org
Apache License 2.0
23.11k stars 6.72k forks source link

External IDP tokens are not refreshed automatically #14644

Open samtay opened 2 years ago

samtay commented 2 years ago

Describe the bug

We're using Github (specifically a Github app) as an external IDP. We have configured Github to use expiring access tokens (good for 8 hours). When we want to get data from Github on behalf of the user, such as a list of repositories where the Github app is installed, we need a Github user-to-server access token.

We can retrieve these external IDP tokens from keycloak via this endpoint.

Unfortunately, if it has been more than 8 hours, then the access token is expired and the request to Github will fail. If I use the refresh token (which is also returned from the endpoint above), to get new tokens from Github, this works once. I get a new access and refresh token, and I can make a request to Github with the access token. However, this expires the refresh token that Keycloak has. And there doesn't seem to be a way for me to give Keycloak the new access/refresh tokens that I've retrieved on behalf of the user.

Is this just an oversight? Is there any way to use Github as an external IDP with expiring access tokens?

Version

19.0.2

Expected behavior

  1. Keycloak refreshes tokens before returning them from GET /realms/{realm}/broker/{provider_alias}/token (provided the current refresh_token has not expired).

Actual behavior

Keycloak appears to just return the exact response that it originally receives from Github, which is most likely an expired access token. If the refresh token is still good, it is worthless because using it will expire the one held in Keycloak.

How to Reproduce?

Use Github as an external IDP with expiring tokens and try to use the endpoint as described above.

Anything else?

No response

kherock commented 1 year ago

@stianst I'm also interested in this and am willing to implement if one of the maintainers can confirm some high level requirements for the implementation. So far this is what I'd expect to see:

jbman commented 1 year ago

I propose to that such a feature automatically refreshes a stored token when the token response contains a refresh token. The refresh should be executed some time before the stored token expires. This time could be configurable with a new setting "Refresh stored tokens X seconds before expiration" with a default of 60. With value of 0 refresh would be deactivated.

sschu commented 1 year ago

If I get this right, there is still the problem that once a retrieved refreshed token is used, it is automatically invalidated (at least potentially and apparently in the case of GitHub). Is there an idea how to solve this?

kherock commented 1 year ago

Keycloak should just store the new access token response that would contain the new refresh token, unless I'm misunderstanding?

sschu commented 1 year ago

But if you use the refresh token outside of Keycloak, Keycloak won't be able to do new refreshes with the token it stored.

kherock commented 1 year ago

In what circumstances would refresh tokens be used outside of Keycloak? I wouldn't expect for the refresh token stored by Keycloak to be used by anything besides Keycloak since that would imply that Keycloak's IDP client secret is also being used somewhere else.

sschu commented 1 year ago

I don't have a use case for that. But I assumed the creator of this issue wanted to do it as he said "If the refresh token is still good, it is worthless because using it will expire the one held in Keycloak."

jbman commented 1 year ago

@sschu: I think, this is only explaining that using the refresh token outside of Keycloak is no viable workaround. "If the refresh token is still good, it is worthless because using it will expire the one held in Keycloak."

When Keycloak refreshes the access token in time, the app will never need the refresh token as assumed by @kherock.

sschu commented 1 year ago

@jbman You are right, that would be an alternative interpretation.

jaredtbates commented 1 year ago

@sschu: I think, this is only explaining that using the refresh token outside of Keycloak is no viable workaround. "If the refresh token is still good, it is worthless because using it will expire the one held in Keycloak."

When Keycloak refreshes the access token in time, the app will never need the refresh token as assumed by @kherock.

This would be our use case. We have an external IdP for which we'd like to keep the tokens refreshed if possible, so the users don't have to log in every day.

ingesa-mel commented 1 year ago

what is the current status of this issue? also very much interested in such an implementation or even a work-around.

LPCmedia commented 1 year ago

Is there a way to achieve this via the "identity-brokering-apis" that is documented somewhere ? or another approach ?

In our case we would want to ensure that keycloak checks the external idp to ensure that the access_token is still valid when a client refreshes the keycloak token. Really if the tokens are revoked in the external idp we want to log the user out.

mposolda commented 1 year ago

Adding to Backlog with label Help wanted. Keycloak team won't have time to look at this in the near future. But community contribution is welcome to look at it and eventually fix it. See the "Contributors" section under https://www.keycloak.org/community .

Let me try to summary how to possibly implement this based on what you guys discussed (also few notes of my own):

Dreneg commented 1 year ago

We have written a custom idp based on OIDCIdentityProvider to support this scenario. This logic also could go to the AbstractOAuth2IdentityProvider class or org.keycloak.services.resources.IdentityBrokerService.getToken method if it is implemented inside keycloak (maybe with an additional boolean flag to enable/disable behaviour, if there is a reason to do so).

Logic sum up: At first, current token is retrieved. If it is expired, we try to refresh it (using OIDCIdentityProvider.getRefreshTokenRequest), and update FederatedIdentityModel with the refreshed token. Not sure though if this solution can cover all general use case, for us it was sufficient so far.

In case anyone are interested in the implementation, you can find it here.

pedroigor commented 7 months ago

I'm open to reviewing a contribution to this issue. The solutions proposed here all make sense to me. For that, marking the issue as normal and with help-wanted.

keycloak-github-bot[bot] commented 7 months ago

Due to the amount of issues reported by the community we are not able to prioritise resolving this issue at the moment.

If you are affected by this issue, upvote it by adding a :thumbsup: to the description. We would also welcome a contribution to fix the issue.