9229 - Add bearer token auth mechanism for OIDC user with feature flag#9591
9229 - Add bearer token auth mechanism for OIDC user with feature flag#9591kcondon merged 7 commits intoIQSS:developfrom
Conversation
pdurbin
left a comment
There was a problem hiding this comment.
I tested the previous iteration of this pull request (#9532) and it worked so I'm approving this.
It looks like @johannes-darms incorporated all of my suggestions (thanks!).
@kcondon please note that the previous PR has all the details on how to test. I did my testing with Docker. I'm happy to walk your through what I did.
poikilotherm
left a comment
There was a problem hiding this comment.
I'm sorry to be pedantic, but I do see some places that IMHO could result in a bad API user experience. Thanks for keeping up the good work.
| - Enables API authentication via session cookie (JSESSIONID). **Caution: Enabling this feature flag exposes the installation to CSRF risks!** We expect this feature flag to be temporary (only used by frontend developers, see `#9063 <https://github.com/IQSS/dataverse/issues/9063>`_) and removed once support for bearer tokens has been implemented (see `#9229 <https://github.com/IQSS/dataverse/issues/9229>`_). | ||
| - ``Off`` | ||
| * - api-bearer-auth | ||
| - Enables API authentication via Bearer Token for OIDC User Accounts. **Information: This feature works only for OIDC UserAccounts!** |
There was a problem hiding this comment.
| - Enables API authentication via Bearer Token for OIDC User Accounts. **Information: This feature works only for OIDC UserAccounts!** | |
| - Enables API authentication via Bearer Token for OIDC User Accounts. **Note: This feature works only when using an :doc:`OpenID Connect authentication provider <oidc>`!** |
| .. _bearer-token-auth: | ||
|
|
||
| Bearer Token Authentication | ||
| --------------------------- | ||
|
|
||
| Bearer tokens are defined in `RFC 6750`_ and can be used as an alternative to API tokens. This is an experimental feature hidden behind a feature flag. | ||
|
|
||
| .. _RFC 6750: https://tools.ietf.org/html/rfc6750 | ||
|
|
||
| To enable bearer tokens, you must install and configure Keycloak (for now, see :ref:`oidc-dev` in the Developer Guide) and enable ``api-bearer-auth`` under :ref:`feature-flags`. | ||
|
|
||
| You can test that bearer tokens are working by following the example under :ref:`bearer-tokens` in the API Guide. | ||
|
|
There was a problem hiding this comment.
I do understand this is explained in the API docs (perfect place), this repetition and elaboration here seems somewhat misplaced IMHO. Shouldn't we keep this in the API guide?
| - DATAVERSE_DB_HOST=postgres | ||
| - DATAVERSE_DB_PASSWORD=secret | ||
| - DATAVERSE_DB_USER=${DATAVERSE_DB_USER} | ||
| - DATAVERSE_FEATURE_API_BEARER_AUTH=1 |
There was a problem hiding this comment.
I'd rather not have this feature on by default. I'd rather keep this file less feature enabled, but more bare-bones.
| dev_keycloak: | ||
| container_name: "dev_keycloack" | ||
| image: 'quay.io/keycloak/keycloak:19.0' | ||
| hostname: keycloak | ||
| environment: | ||
| - KEYCLOAK_ADMIN=kcadmin | ||
| - KEYCLOAK_ADMIN_PASSWORD=kcpassword | ||
| - KEYCLOAK_LOGLEVEL=DEBUG | ||
| - KC_HOSTNAME_STRICT=false | ||
| networks: | ||
| dataverse: | ||
| aliases: | ||
| - keycloak.mydomain.com #create a DNS alias within the network (add the same alias to your /etc/hosts to get a working OIDC flow) | ||
| command: start-dev --import-realm --http-port=8090 # change port to 8090, so within the network and external the same port is used | ||
| ports: | ||
| - "8090:8090" | ||
| volumes: | ||
| - './conf/keycloak/oidc-realm.json:/opt/keycloak/data/import/oidc-realm.json' | ||
|
|
There was a problem hiding this comment.
Please note:
- Keycloak 19 is outdated.
- I dislike adding more containers than necessary here. This is an experimental feature and right now, for most of development this is waste of CPU cycles. Can we make this
scale: $ENABLE_KEYCLOAK, haveENABLE_KEYCLOAK=0in.envso you need to enable this by addingENABLE_KEYCLOAK=1when starting this?
| @Inject | ||
| protected UserServiceBean userSvc; | ||
| private static final Logger logger = Logger.getLogger(BearerTokenAuthMechanism.class.getCanonicalName()); | ||
| @Override |
There was a problem hiding this comment.
| @Override | |
| @Override |
| /** | ||
| * Enables API authentication via Bearer Token. | ||
| * @apiNote Raise flag by setting "dataverse.feature.api-bearer-auth" | ||
| * @since Dataverse @TODO: |
There was a problem hiding this comment.
TODO. Maybe fine to put 5.14 here, I have a feeling this might make it.
| sut.authSvc = Mockito.mock(AuthenticationServiceBean.class); | ||
| sut.userSvc = Mockito.mock(UserServiceBean.class); |
There was a problem hiding this comment.
Please move these into setUp() to make the tests more readable. (Goes as well for all the other occurences...)
| } | ||
|
|
||
| @Test | ||
| @JvmSetting(key = JvmSettings.FEATURE_FLAG, value = "true", varArgs = "api-bearer-auth") |
There was a problem hiding this comment.
I'm sorry that this is a bit messy - I did not yet implement setting something like this on a class level, this needs tuning. (Would avoid the repetition)
| return null; | ||
| } | ||
| } | ||
| return null; |
There was a problem hiding this comment.
This is not covered by a test, but is part of the API contract. Please make sure there is a unit test for this.
| * @throws IOException | ||
| * @throws OAuth2Exception |
There was a problem hiding this comment.
When are these exceptions thrown?
src/main/java/edu/harvard/iq/dataverse/api/auth/BearerTokenAuthMechanism.java
Outdated
Show resolved
Hide resolved
|
@poikilotherm I'm not sure if this was your intention by selecting "request changes" but merging is blocked until you switch to "comment" or "approve". |
Co-authored-by: Oliver Bertuch <poikilotherm@users.noreply.github.com>
pdurbin
left a comment
There was a problem hiding this comment.
@poikilotherm and I chatted about this PR and left some comment.
src/main/java/edu/harvard/iq/dataverse/api/auth/BearerTokenAuthMechanism.java
Outdated
Show resolved
Hide resolved
| return userInfo.get(); | ||
| } | ||
| } catch ( IOException| OAuth2Exception e) { | ||
| logger.log(Level.FINE, "Bearer token detected, provider " + provider.getId() + " indicates an invalid Token, skipping", e); |
There was a problem hiding this comment.
It sounds like the 401 is sent later because we aren't doing anything in the catch here.
| * @throws IOException | ||
| * @throws OAuth2Exception | ||
| */ | ||
| public Optional<UserRecordIdentifier> getUserIdentifierForValidToken(BearerAccessToken accessToken) throws IOException, OAuth2Exception{ |
There was a problem hiding this comment.
The idea is to just return an empty optional rather than throwing an OAuth2Exception.
|
Tested with assistance from @pdurbin. Looks good from a basic functionality perspective. I know there are additional suggestions/to dos but seems ok to merge this step? |
Uh oh!
There was an error while loading. Please reload this page.