Skip to content

feat: implement Bearer token based API access#632

Open
herglotzmarco wants to merge 19 commits into
jenkinsci:masterfrom
herglotzmarco:hergl/feature/bearer-auth
Open

feat: implement Bearer token based API access#632
herglotzmarco wants to merge 19 commits into
jenkinsci:masterfrom
herglotzmarco:hergl/feature/bearer-auth

Conversation

@herglotzmarco
Copy link
Copy Markdown

This PR adds the option to authenticate against the Jenkins API using JWT Bearer tokens instead of Basic Auth (Jenkins API tokens). This closes #630

By default Bearer token based access is disabled. This needs to be explicitly enabled in the plugins configuration in the "security configuration" section.

This change respects other settings like ignoring token validation, expiration or clock skew.

Testing done

The change is tested extensively with automatic JUnit tests. Also manual tests have been performed to validate that existing features did not break and that the new feature works as intended. It has been validated that it is now possible to use Authorization: Bearer HTTP headers to authenticate against the Jenkins API. Is has also been validated that invalid tokens, either because they have been tampered with, they have been expired or any other kind of invalidity do not grant access to protected resources. I tried to respect all existing settings, e.g. to bypass JWT signature validation or expiration.

Submitter checklist

  • Make sure you are opening from a topic/feature/bugfix branch (right side) and not your main branch!
  • Ensure that the pull request title represents the desired changelog entry
  • Please describe what you did
  • Link to relevant issues in GitHub or Jira
  • Link to relevant pull requests, esp. upstream and downstream changes
  • Ensure you have provided tests that demonstrate the feature works or the issue is fixed

@herglotzmarco herglotzmarco requested a review from a team as a code owner August 6, 2025 14:28
@codecov
Copy link
Copy Markdown

codecov Bot commented Aug 6, 2025

Codecov Report

❌ Patch coverage is 86.30137% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.14%. Comparing base (9a9a85d) to head (7801f34).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...va/org/jenkinsci/plugins/oic/OicSecurityRealm.java 89.70% 4 Missing and 3 partials ⚠️
...nkinsci/plugins/oic/BearerTokenCrumbExclusion.java 40.00% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master     #632      +/-   ##
============================================
+ Coverage     74.60%   75.14%   +0.53%     
- Complexity      315      325      +10     
============================================
  Files            33       34       +1     
  Lines          1280     1340      +60     
  Branches        176      180       +4     
============================================
+ Hits            955     1007      +52     
- Misses          241      247       +6     
- Partials         84       86       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@herglotzmarco
Copy link
Copy Markdown
Author

Code Coverage does not seem to update in the PR here: I fixed the missed lines. The only line that is still missed cannot be tested as it is guaranteed not to happen in the code, so not sure what I should do with it. Could remove it, however the current way is recommended by Nimbus JWT, so I leave it up to the reviewer to decide whether I will stay with the recommendation or go with code coverage

@jtnord jtnord mentioned this pull request Sep 5, 2025
6 tasks
@herglotzmarco
Copy link
Copy Markdown
Author

@jtnord Since the OidcProperty change has been merged, this PR is incompatible... Is it worth for me to put in effort again to make/keep it compatible again? Until now, neither the linked issue nor this PR received any attention it looks like. Are there any plans to accept this enhancement?

@jtnord
Copy link
Copy Markdown
Member

jtnord commented Sep 10, 2025

@jtnord Since the OidcProperty change has been merged, this PR is incompatible... Is it worth for me to put in effort again to make/keep it compatible again? Until now, neither the linked issue nor this PR received any attention it looks like. Are there any plans to accept this enhancement?

I had started to look at it, but had not finished my review. I was concerned about security, esp re-using the disabling of token validation.
In the regular code flow a token would be coming from a trusted (by TLS) server (even if the token itself was not signed because you opted out).
if you opt into bearer tokens without trust then there is no security whatsoever - anyone can send any HTTP request with any arbitrary JWT containing whatever user and group info they desire IIUC.

In 2025 I do struggle to see the need to disable TLS or disable signing.

There are also some other bits that would presumably need tweaking (from Jenkins core)? IIUC this should only be used for API type access (ie to replace an APIToken). In which case it would fall foul of the Jenkins-Crumb so API calls would not work as they do with Bearer tokens compared to API Tokens. e.g. https://github.com/jenkinsci/jenkins/blob/jenkins-2.516.2/core/src/main/java/jenkins/security/ApiCrumbExclusion.java

I'm not against the notion of accepting Bearer tokens, but the feature would need to maintain security.

Comment thread src/main/java/org/jenkinsci/plugins/oic/OicSecurityRealm.java
@herglotzmarco
Copy link
Copy Markdown
Author

I had started to look at it, but had not finished my review. I was concerned about security, esp re-using the disabling of token validation. In the regular code flow a token would be coming from a trusted (by TLS) server (even if the token itself was not signed because you opted out). if you opt into bearer tokens without trust then there is no security whatsoever - anyone can send any HTTP request with any arbitrary JWT containing whatever user and group info they desire IIUC.

In 2025 I do struggle to see the need to disable TLS or disable signing.

I am absolutely with you and I am happy to remove that setting. My goal submitting this request was to respect as many existing settings as possible. From my point of view, that settings can not only be removed from being validated for the Bearer token access, but can be removed from the plugin entirely...

There are also some other bits that would presumably need tweaking (from Jenkins core)? IIUC this should only be used for API type access (ie to replace an APIToken). In which case it would fall foul of the Jenkins-Crumb so API calls would not work as they do with Bearer tokens compared to API Tokens. e.g. https://github.com/jenkinsci/jenkins/blob/jenkins-2.516.2/core/src/main/java/jenkins/security/ApiCrumbExclusion.java

My personal use case is to replace API Tokens, but probably there could be other use cases as well. Tbh in my testing I have not had any issues with Crumb Processing whatsoever. I am also not too far into that topic actually (first time I am touching a Jenkins Plugin), so if it comes to the fine details I might need some help to get that straight.

@jtnord jtnord requested a review from a team September 11, 2025 10:07
@eva-mueller
Copy link
Copy Markdown
Contributor

Hi @herglotzmarco / @jtnord - I am not deeply into this topic but am curious if your feature solves something what I a missing (a user can use a "token" to trigger builds although it is an SSO user and their groups are always in sync with the IdP)

My use case is: A user logging in with SSO should also be able to use "a" token to trigger builds.
Currently, you can tick: "Allow access using a Jenkins API token without an OIDC Session"
=> The problem here is: If the groups of the SSO users are changing, they will never be updated if the user solely uses the API token

My questions are:

  • Does this approach (JWT Bearer tokens) ensure that the user groups are kept up-to-date if the user solely uses JWTs instead of a UI based login?
  • Would it be generally possible to forbid using Jenkins API tokens when using SSO?

@herglotzmarco
Copy link
Copy Markdown
Author

  • Does this approach (JWT Bearer tokens) ensure that the user groups are kept up-to-date if the user solely uses JWTs instead of a UI based login?

@eva-mueller-coremedia To be honest I have no idea where the groups are updated at all. The call that will be performed using the OIDC token will run with the permissions granted by the groups passed in the token, but I am not sure if this state is then also persisted within Jenkins. Could be that the "updated" groups will only appear in Jenkins UI when the user signs in "normally" the next time.
However I think this would still solve your issue I think: Say the user lost a group allowing him to trigger build X, but does never log in via the UI. When they try to trigger build X with a newly created token (that contains the updated groups), that call should be rejected.

  • Would it be generally possible to forbid using Jenkins API tokens when using SSO?

I guess that should in theory be possible, but is not done in this PR

@eva-mueller
Copy link
Copy Markdown
Contributor

  • Does this approach (JWT Bearer tokens) ensure that the user groups are kept up-to-date if the user solely uses JWTs instead of a UI based login?

@eva-mueller-coremedia To be honest I have no idea where the groups are updated at all. The call that will be performed using the OIDC token will run with the permissions granted by the groups passed in the token, but I am not sure if this state is then also persisted within Jenkins. Could be that the "updated" groups will only appear in Jenkins UI when the user signs in "normally" the next time. However I think this would still solve your issue I think: Say the user lost a group allowing him to trigger build X, but does never log in via the UI. When they try to trigger build X with a newly created token (that contains the updated groups), that call should be rejected.

Thank you for you quick reply! My security concern is (and this is the reason why I can't use the Jenkins API token approach):

Assume: A user has the right to trigger a pipeline A doing the something in production (since the pipeline is restricted to be executed by users in the group prod-admin). The group prod-admin he has been assigned to the user by the IdP department.
Later this year: The user has been removed from the group prod-admin.

=> It needs to be ensured that - although the JWT does not contains the group prod-admin, the Jenkins user DB has not been updated yet since no UI login took place - the user is not allowed to execute to trigger this pipeline A.

  • Would it be generally possible to forbid using Jenkins API tokens when using SSO?

I guess that should in theory be possible, but is not done in this PR

Maybe I will create a feature request here. @jtnord is it worth it?

@herglotzmarco
Copy link
Copy Markdown
Author

Thank you for you quick reply! My security concern is (and this is the reason why I can't use the Jenkins API token approach):

Assume: A user has the right to trigger a pipeline A doing the something in production (since the pipeline is restricted to be executed by users in the group prod-admin). The group prod-admin he has been assigned to the user by the IdP department. Later this year: The user has been removed from the group prod-admin.

=> It needs to be ensured that - although the JWT does not contains the group prod-admin, the Jenkins user DB has not been updated yet since no UI login took place - the user is not allowed to execute to trigger this pipeline A.

Using the Bearer Token, the user will not be allowed to trigger pipeline A. Using Jenkins API Tokens I am not sure. I can imagin they would still work though

@eva-mueller
Copy link
Copy Markdown
Contributor

Using Jenkins API Tokens I am not sure. I can imagin they would still work though

Yes, it will work - unfortunately - this is the reason why I manually remove such tokens (akward way to forbid using these tokens right now - I know 😞 ) and the reason asking for

Would it be generally possible to forbid using Jenkins API tokens when using SSO?

@herglotzmarco
Copy link
Copy Markdown
Author

@jtnord anything missing from your side? Or can this PR be merged and released?

Copy link
Copy Markdown
Contributor

@michael-doubez michael-doubez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ability to use token was been requested many time.

The vanilla case must at use the token as an access token and validate it with IdP (but in this case, I expect no idtoken to be present).

Additionally, (yet) another configuration parameter could allow using the access token as a JWT and perform validation and claim search on it.

That way, we could still have a compliant oidv implmentation but allowing the popular case of access token as JWT (which is more like OAuth2).

String authHeader = httpRequest.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
try {
JWT jwt = JWTParser.parse(authHeader.substring(7));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auth token are not necessarily JWT (even in OAuth).
Under openid connect, only the idtoken should be validated.

Morerover, your are not validating it with the IDP which would be expected if it is configured with authorization code flow in mind.

I understand this is frustrating because many IDP uses such a scheme and although it is not strictly oidc compliant, it would be practical.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I don't fully understand this point:

I am validating the signature of the JWT token using JWKS. Afaik this is the only requirement for validating with the IdP. I think JWT tokens are (in contrast to opaque tokens) explicitly meant to be validated "offline" without IdP interaction. What has auth code flow to do with this? At this point we have a token and where it comes from is totally unrelated isn't it?

I know that Auth Tokens are not necessarily JWT, but isn't accepting e.g. opaque tokens yet another feature? The description of the checkbox for accepting bearer tokens explicitly states JWT as requirement. I would really like to get this finished and tbh I don't feel like introducing yet another feature, especially one that according to my experience is probably not needed by 90% of usages. In all my experience I pretty much exclusively saw JWT tokens being used by various tools and providers.

I would prefer using JWT as it is now and opaque tokens can be implemented in a different PR. Right now we have nothing, after this PR we have the most common use cases covered already and we can expand on that later on. Feels wrong to make this more complicated than necessary for what feels to me like almost no benefit.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opaque token may be validated by an introspection endpoint, that's what I mean by code flow.
Access token may have a huge life time (I have seen1y). If there is no way to revoke them, you are letting the door open in case of leak.

IMO the secure version should be the default and not the convenient one. Using the access token as a verified credentials is the feature which should be carefully considered.

See https://datatracker.ietf.org/doc/html/rfc7662

In OAuth 2.0 [RFC6749], the contents of tokens are opaque to clients.
This means that the client does not need to know anything about the
content or structure of the token itself, if there is any. However,
there is still a large amount of metadata that may be attached to a
token, such as its current validity, approved scopes, and information
about the context in which the token was issued. These pieces of
information are often vital to protected resources making
authorization decisions based on the tokens being presented. Since
OAuth 2.0 does not define a protocol for the resource server to learn
meta-information about a token that it has received from an
authorization server, several different approaches have been
developed to bridge this gap. These include using structured token
formats such as JWT [RFC7519] or proprietary inter-service
communication mechanisms (such as shared databases and protected
enterprise service buses) that convey token information.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that verifying access token, would open the usage of implicit flow (which is usually not recommended).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that verifying access token, would open the usage of implicit flow (which is usually not recommended).

Not only implicit flow but all flows. You could also use client credential flow, CIBA flow or whatever and I consider this a feature and not a bug as we then delegate deciding about the flow to the IdP which is the right place for such decisions. If you don't want to allow implicit flow, disable it in the IdP and not in the jenkins plugin. Especially because validating an already existing Bearer token is explicitly meant for API calls where I usually do not have a browser than can easily run the redirect-based auth code flow.

If you insist, I will look into opaque tokens by default. That might take some time though as I thought this PR is pretty much done and just needs minor adjustments. I have no capacity for working on major changes currently.

Note: Just to underline my point about opaque tokens being a rare species in nature: THE most popular open-source IdP Keycloak does not even support them, at least not the way they are intended. They have "lightweigth access tokens" which somewhat behaves like oapque tokens but is not really the same IMO.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The any flow from anywhere is precisely my issue with the token validation. You are assuming the user used a client credential and that it is intended for the jenkins instance.

In the current code, there is no check for :

  • can we assume there is a token_type claim ? If not, does the admin agree to take the responsibility to allow "client credential" token without proof ?
  • the aud claim is difficult to validate (IMO): you assume it is the same as the client id, is it the case ? It is supposed to be the service, somehow ? What about the client_id claim itself ?
  • your code doesn't check for the additional failedCheckOfTokenField() but can we assume they are present in the access token ?

Moreover, there are a lot of assumptions which deviate from the code flow and the (draft) RFC doesn't really answer those question except to say the token should be carefully verified and encode the same data. See https://datatracker.ietf.org/doc/html/rfc9068

All of that to say, I would rather the access token be another source of claims (same as idtoken or userInfo) and be used uniformly rather than being a specific case of client credentials flow + adding the introspect endpoint to this flow.

This is going back on what I said in #218 but well ... if it is accepted for one flow, why not the other.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not assuming client credential flow. Actually my use-case is precisely NOT using a client credential flow. If I wanted only a hardcoded, non-expiring secret stored somewhere I could also just use the jenkins API Tokens instead of bothering with login flows at all.

To be honest I still don't understand what your issue is, but what I am hearing is that you don't like the feature, at least in it's current form, for security reasons. Little disappointend that I am told like 8 months after creating the PR and spending time for it already...

I don't understand why because even after all your explanations I still cannot see how you could bypass the token validations as an attacker, but clearly you seem to be a lot deeper into OIDC and OAuth than I am, so thats probably lacking knowledge on my side.

I'd suggest you either close the PR if you don't agree with the feature at all, or you finish it up yourself the way you think it can work in a secure manner. I don't want to take responsibility for accidentally introducing vulnerabilities because of maybe not understanding OIDC and OAuth correctly.

I can take responsiblity however for the company I am working for and I have deployed and used a custom build of this plugin for months now, so I will just continue doing that, occasionally merging upstream fixes into my fork.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we be able to move this forward under the assumption that we are only working with JWT-structured tokens? Some things that publish opaque tokens (eg Entra ID v1 tokens) don't even provide an introspection endpoint. Maybe the code should say:

  1. Configure an optional introspection endpoint.
  2. Try to parse and validate it as a JWT. If it does, then proceed on to scope validation (step 4).
  3. If the parse to JWT fails, try the introspection endpoint, if it exists.
  4. If no introspection endpoint exists, fail here.
  5. Validate scopes etc.


// check if this is a valid api token based request
String authHeader = httpRequest.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Basic ")) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it case inseistive see #594

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

if (isValidApiTokenRequest(httpRequest, user)) {
// check if this is a valid Bearer token based request
String authHeader = httpRequest.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Case insensitive as well

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@jonesbusy
Copy link
Copy Markdown
Contributor

Any change we can resurrect this PR?

I initially opened #632 for MCP access (I mean OAuth 2.0 MCP access is in 2026 the standard to avoid the usage of API keys)

I don't have a strong opinion if it should be part of oic-auth-plugin or via an extension plugin (didn't read the code, not sure if even possible)

Maybe I lack OAuth knowledge as well but many tool out platform/CI/CD tools already support Bearer JWT API access

@olamy
Copy link
Copy Markdown
Member

olamy commented May 13, 2026

@jonesbusy regarding MCP access.
Looking at this PR. If I understand this correctly, this would work only with the OicSecurityRealm as the active realm.
So if it's your case, that's fine :)
But I can see a limitation for Jenkins using other realms such as LDAP/AD/SAML/internal DB...

@jonesbusy
Copy link
Copy Markdown
Contributor

For this PR yes, it's tight to the OicSecurityRealm

I personally don't need this PR anymore since I solved my use case with https://plugins.jenkins.io/jwt-auth/ which do not have any dep on any security realm
Mentioned plugin doesn't provide standardized MCP auth (like rfc9728 OAuth 2.0 Protected Resource Metadata, meaning the authentication flow need to be done by an upstream gateway like https://agentgateway.dev/) but only the ability to validate JWT incoming tokens (Jenkins act as a resource server). This was all I need for now

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow API calls using an OIDC Bearer token

8 participants