Feature #43881
openStrengthen API authentication: API tokens with expiration, scopes, rate limiting and audit logging
Description
API authentication currently relies on a static API key assigned to each user account, with no expiration date and no second factor requirement.
In my organization, with the recent enforcement of mandatory 2FA for administrator accounts (#35439), there is now a significant security gap between web access and API access: a user's web session is protected by 2FA, but the very same user's API key provides full, unrestricted access without any second factor.
This proposal complements the OAuth2 provider shipped in 6.1 (#24808), which addresses third-party application authentication.
Main identified issues¶
- 2FA bypass — Since #35001, basic authentication with username/password is blocked when 2FA is active. However, API keys still provide full access without any second factor, effectively bypassing the 2FA protection that administrators have required.
- Static keys with no expiration — API keys never expire. Once generated, they remain valid indefinitely until manually reset. A leaked key provides permanent access.
- Excessive privileges — Each API key inherits all permissions of the associated user account, with no ability to restrict access to read-only operations, specific projects, or specific API endpoints.
- No request rate limiting — There is no built-in protection against brute-force attacks on API keys or bulk data extraction.
- Insufficient traceability — API calls are not logged in a structured way (beyond standard application logs).
Proposed improvements to Redmine Core¶
I would like to work on this topic, but these changes should be discussed with Redmine maintainers and integrated into upcoming official releases.
1. Personal Access Tokens with a hybrid management model¶
I propose to replace the current single static API key with Personal Access Tokens, following the hybrid self-service + admin governance model used by GitHub, GitLab, and other major platforms.
Core features of Personal Access Tokens¶
- Multiple named tokens per user
- Mandatory expiration date
- Hashed storage — Token values are stored as SHA256 hashes, consistent with the approach used by Doorkeeper for OAuth2 tokens in #24808. The plaintext is shown only once at creation.
- Last-used tracking
- Management UI — Users manage their tokens in "My account". Administrators have a dedicated panel in Administration to view and manage all tokens across users.
- Backward compatibility — The existing single API key mechanism continues to work during the transition period.
2. Scoped permissions per token¶
- Each token can be restricted to specific permissions (read-only, specific trackers, time logging only, etc.)
- Reuse the scope mechanism already implemented for OAuth2 via Doorkeeper (#24808)
- Optionally restrict a token to specific projects only
- Administrators define which scopes are available globally
3. Rate limiting¶
- Limit the number of API requests per token (and maybe per IP address and/or per endpoint)
- Return standard HTTP 429 (Too Many Requests) responses when limits are exceeded
4. Structured API audit logging¶
- Log all API calls in a dedicated, queryable format (not just standard application logs): token used, endpoint, HTTP method, source IP, timestamp, response status
- Provide an admin UI or API to query and export audit logs
- Enable notifications for anomalous activity: excessive request volume, requests from unknown IPs, repeated authentication failures
5. Granular API endpoint control¶
- Currently, the REST API can only be enabled or disabled globally — it is all or nothing
- Add the ability to disable specific API endpoints or groups of endpoints (e.g. allow issue read but disable user management API)
- This allows administrators to expose only the API surface area that is actually needed
6. CORS configuration?¶
- Allow administrators to define which domains are authorized to access the API from a browser context
- Currently there is no CORS configuration, which means either all origins are allowed or administrators must handle this at the reverse proxy level
How to start?¶
Given the scope of these changes, I propose to start small with a step-by-step approach.
We could start by adding Personal Access Tokens with- self-service creation,
- expiration,
- multiple tokens per user,
- hashed storage,
- admin max-lifetime policy,
- admin token overview panel,
and keep backward compatibility with legacy keys
Other features could be added later.
I am willing to provide patches for these first steps.
- Whether the hybrid self-service + admin governance model seems appropriate
- Whether legacy API key deprecation is desirable, and what transition period would be reasonable
Files
Related issues
Updated by Vincent Robert 4 months ago
- Related to Feature #35001: Disable API authentication with username and password when two-factor authentication is enabled for the user added
Updated by Vincent Robert 3 months ago
- Related to Feature #43938: Track last usage of API and Atom access keys added
Updated by Dennis Buehring 3 months ago
We have a problem with more and more users using their api keys to query and automate things in redmine, thanks chatgpt ;)
i would like to be able to allow/enable token generation only for specific users, same as with other roles i guess.
Updated by Marco Descher 2 months ago
Related request: https://www.redmine.org/issues/44063
Updated by Marco Descher 2 months ago
Proposed oauth token support to access apikey in https://github.com/kontron/redmine_oauth/issues/34
Updated by Marius BĂLTEANU about 1 month ago
- Related to Feature #44063: Implement enforcable or automated api key rotation added
Updated by Iurii Dremov 9 days ago
I'm working on pillar 3 of this proposal (rate limiting) and have a working implementation built against 6.1.2 that I'm now porting to trunk: a fixed-window per-user limit (per-IP for unauthenticated callers) for REST API requests, configurable via a new setting (disabled by default), responding 429 with Retry-After.
I'll attach the patch with tests once the trunk port is ready — feedback on the approach is welcome.
Updated by Iurii Dremov 9 days ago
- File 0001-Add-rate-limiting-for-the-REST-API-43881.patch 0001-Add-rate-limiting-for-the-REST-API-43881.patch added
Attached is a patch against current trunk implementing the rate limiting part (pillar 3) of this proposal.
What it does:
- Fixed-window (60 s) request counting per authenticated user, or per source IP for unauthenticated requests; covers the API key, HTTP Basic and OAuth2 authentication paths.
- New integer setting rest_api_rate_limit (requests per minute, 0 = disabled; default 0, so existing installations are unaffected), shown on the API settings tab next to rest_api_enabled.
- Over the limit the API responds 429 Too Many Requests with Retry-After and X-RateLimit-Limit headers and an errors body in JSON/XML (reusing common/error_messages.api.rsb); each rejection is logged via Rails.logger.warn
- Counters live in a cache store (Rails.cache by default, injectable — the tests swap in a memory store since the test environment runs :null_store). With a store that cannot count, the limiter fails open.
- Web UI requests are never throttled (the filter is guarded by api_request?)
Known limitations, by design: fixed-window boundary bursts (up to 2x the limit across a window edge); with the default :file_store/:memory_store counters are per host/process — a shared limit across nodes needs memcached/redis;
remote_ip trust depends on the reverse proxy setup.
Tested with Ruby 3.4/SQLite: the new unit + integration tests pass (14 runs), and the whole test/integration/api_test suite stays green (382 runs). RuboCop is clean on the touched files.
Feedback welcome — happy to adjust naming, defaults or scope (per-endpoint limits, X-RateLimit-Remaining, …) based on maintainer guidance.
Updated by Marius BĂLTEANU 8 days ago
Thanks Iurii Dremov for working on this!
Is there any reason why you didn't use the existing rate_limit API provided since Rails 7.2? https://api.rubyonrails.org/classes/ActionController/RateLimiting/ClassMethods.html The feature was improved in Rails 8.0 to support multiple rate limits.
Updated by Iurii Dremov 8 days ago
- File 0001-Add-rate-limiting-for-the-REST-API-43881.patch 0001-Add-rate-limiting-for-the-REST-API-43881.patch added
No good reason. Thanks for the pointer.
The only thing in the way was the setting: to: and within: are captured when the class body runs, while rest_api_rate_limit is meant to be editable in the admin UI without a restart. So I kept the setting and called rate_limiting from a before_action placed after user_setup, so that the bucket can be the current user and fall back to the IP:
def check_api_rate_limit
limit = Setting.rest_api_rate_limit.to_i
return unless api_request? && limit > 0
rate_limiting :to => limit, :within => 1.minute, :scope => 'api', :name => nil,
:by => lambda {User.current.logged? ? "user/#{User.current.id}" : "ip/#{request.remote_ip}"},
:with => lambda {render_api_rate_limit_exceeded(limit)},
:store => Rails.cache
end
This drops my own counter and its unit test, and the rate_limit.action_controller notification is a better hook for the audit logging pillar of this issue than the logger.warn I had. The downside is the dependency on a private method.
If you prefer to stay on the public macro, the limit has to move to configuration.yml and changing it then needs a restart. I can send that version instead.
Updated patch attached