Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix: Oidc: Properly query the UserInfo Endpoint #4726

Merged

Conversation

LukeShu
Copy link

@LukeShu LukeShu commented Dec 15, 2023

(This is against the development branch; if you think this is worth backporting to release, I have a version of it against the release branch ready-to-go.)

BooksStack's OIDC Client requests the 'profile' and 'email' scope values in order to have access to the 'name', 'email', and other claims. It looks for these claims in the ID Token that is returned along with the Access Token.

However, the OIDC-core specification section 5.4 only requires that the Provider include those claims in the ID Token if an Access Token is not also issued. If an Access Token is issued, the Provider can leave out those claims from the ID Token, and the Client is supposed to obtain them by submitting the Access Token to the UserInfo Endpoint.

So I suppose it's just good luck that the OIDC Providers that BookStack has been tested with just so happen to also stick those claims in the ID Token even though they don't have to. But others (in particular: https://login.infomaniak.com) don't do so, and require fetching the UserInfo Endpoint.)

A workaround is currently possible by having the user write a theme with a ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE hook that fetches the UserInfo Endpoint. This workaround isn't great, for a few reasons:

  1. Asking the user to implement core parts of the OIDC protocol is silly.
  2. The user either needs to re-fetch the .well-known/openid-configuration file to discover the endpoint (adding yet another round-trip to each login) or hard-code the endpoint, which is fragile.
  3. The hook doesn't receive the HTTP client configuration.

So, have BookStack's OidcService fetch the UserInfo Endpoint and inject those claims into the ID Token, if a UserInfo Endpoint is defined. Two points about this:

  • Injecting them into the ID Token's claims is the most obvious approach given the current code structure; though I'm not sure it is the best approach, perhaps it should instead fetch the user info in processAuthorizationResponse() and pass that as an argument to processAccessTokenCallback() which would then need a bit of restructuring. But this made sense because it's also how the ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE hook works.
  • OIDC requires that a UserInfo Endpoint exists, so why bother with that "if a UserInfo Endpoint is defined" bit? Simply out of an abundance of caution that there's an existing BookStack user that is relying on it not fetching the UserInfo Endpoint in order to work with a non-compliant OIDC Provider.

BooksStack's OIDC Client requests the 'profile' and 'email' scope values
in order to have access to the 'name', 'email', and other claims.  It
looks for these claims in the ID Token that is returned along with the
Access Token.

However, the OIDC-core specification section 5.4 [1] only requires that
the Provider include those claims in the ID Token *if* an Access Token is
not also issued.  If an Access Token is issued, the Provider can leave out
those claims from the ID Token, and the Client is supposed to obtain them
by submitting the Access Token to the UserInfo Endpoint.

So I suppose it's just good luck that the OIDC Providers that BookStack
has been tested with just so happen to also stick those claims in the ID
Token even though they don't have to.  But others (in particular:
https://login.infomaniak.com) don't do so, and require fetching the
UserInfo Endpoint.)

A workaround is currently possible by having the user write a theme with a
ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE hook that fetches the UserInfo
Endpoint.  This workaround isn't great, for a few reasons:
 1. Asking the user to implement core parts of the OIDC protocol is silly.
 2. The user either needs to re-fetch the .well-known/openid-configuration
    file to discover the endpoint (adding yet another round-trip to each
    login) or hard-code the endpoint, which is fragile.
 3. The hook doesn't receive the HTTP client configuration.

So, have BookStack's OidcService fetch the UserInfo Endpoint and inject
those claims into the ID Token, if a UserInfo Endpoint is defined.
Two points about this:
 - Injecting them into the ID Token's claims is the most obvious approach
   given the current code structure; though I'm not sure it is the best
   approach, perhaps it should instead fetch the user info in
   processAuthorizationResponse() and pass that as an argument to
   processAccessTokenCallback() which would then need a bit of
   restructuring.  But this made sense because it's also how the
   ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE hook works.
 - OIDC *requires* that a UserInfo Endpoint exists, so why bother with
   that "if a UserInfo Endpoint is defined" bit?  Simply out of an
   abundance of caution that there's an existing BookStack user that is
   relying on it not fetching the UserInfo Endpoint in order to work with
   a non-compliant OIDC Provider.

[1]: https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
@ssddanbrown
Copy link
Member

Thanks for offering this @LukeShu.

Yeah, I stuck to just the ID token claims when originally building to keep the implementation simple, and since from my testing (and most usage cases so far) it's rare for claims not to be within the token, but happy to expand support to use userinfo if there are legitimate scenarios to support.

It'll probably be the next release cycle (early next year) when I come round to properly look to review and merge this since i've already spent a while on OIDC this release cycle, and don't want to expand it upon existing plans.

Related to #3873.

@ssddanbrown
Copy link
Member

Continued in #4955

@ssddanbrown ssddanbrown merged commit dc6013f into BookStackApp:development Apr 19, 2024
1 check failed
@ssddanbrown
Copy link
Member

Thanks again @LukeShu.
This has now been merged after some additional work performed in #4955, and is due to be part of the next feature release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants