Add Internal v2 API Access Token Generation for Users#1279
Merged
aaronskiba merged 19 commits intoaaron/v2-apifrom Feb 23, 2026
Merged
Add Internal v2 API Access Token Generation for Users#1279aaronskiba merged 19 commits intoaaron/v2-apifrom
aaronskiba merged 19 commits intoaaron/v2-apifrom
Conversation
- Creates a first-party Doorkeeper client for issuing internal v2 API tokens - Sets redirect_uri to OOB, scopes to 'read', and marks it as confidential - Ensures the internal application exists in all environments before token service is used
This service manages user-scoped v2 API access tokens for internal app users. - Tokens are equivalent to first-party Personal Access Tokens (PATs) and are issued directly to authenticated users, bypassing the full OAuth 2.0 authorization_code flow. - Supports token creation, rotation, and revocation. - Uses Doorkeeper::AccessToken records for consistent scoping, expiry, and revocation handling. - Designed strictly for internal usage; third-party OAuth clients are not supported.
Adds `Api::V2::InternalUserAccessTokensController#create` with Pundit authorization and routing. Also reuses the existing `users/refresh_token.js.erb` response to update the UI via JS. `@success` is read by `app/views/users/refresh_token.js.erb` (similar approach as `UsersController#refresh_token`)
This change updates `app/views/devise/registrations/_api_token.html.erb` to include support for the v2 API access token. Existing v0/v1 token support is retained. - Introduce V2 token lookup via `Api::V2::InternalUserAccessTokenService` - Display a dedicated V2 API access token section with its own regeneration action
This change breaks refactors `_api_token.html.erb` into additional separate partials: 1) app/views/devise/registrations/_legacy_api_token.html.erb 2) app/views/devise/registrations/_v2_api_token.html.erb In addition to the refactor, the following changes have been made: - `<div id="api-token"` has been renamed to `<div id="legacy-api-token"` - A `<div id="api-tokens">` wrapper has been added in app/views/devise/registrations/_api_token.html.erb. - `app/views/users/refresh_token.js.erb` now references the '#api-tokens' wrapper.
The API Access tab is now visible to all users to support the new v2 API token, which is accessible to everyone. The existing v0/v1 legacy token remains restricted and continues to use the previous authorization and rendering logic within the tab.
Styling changes can be viewed at /users/edit#api-details
`InternalUserAccessTokenService`: add `application!` (lookup + raise) and `application_present?` (safe check with logging) `_v2_api_token.html.erb`: gate token UI on `application_present?` and show a warning when missing.
Add request specs for InternalUserAccessTokensController - Include both authenticated & unauthenticated user scenarios - Include both present & absent internal OAuth app scenarios Add service specs for InternalUserAccessTokenService - Test token retrieval, rotation, and OAuth app presence - Verify old token revocation when rotating Add view specs for API token partials - Test legacy partial rendering based on `user.can_use_api?` - Test OAuth application availability scenarios
Add `defaults: { format: :js }` to the internal_user_access_token route, allowing callers to omit the explicit format parameter.
- Replace BS5 classes with BS3 equivalents: - data-bs-toggle → data-toggle - form-control → form-group - card → panel - btn-secondary → btn-default - Add explicit panel borders for V2 and Legacy sections - Replicate `card-heading` style via `nav nav-tabs`
Generated by 🚫 Danger |
aaronskiba
commented
Feb 18, 2026
| application_id: application!.id, | ||
| resource_owner_id: user.id, | ||
| scopes: READ_SCOPE, | ||
| expires_in: nil # Overrides Doorkeeper's `access_token_expires_in` |
Collaborator
Author
There was a problem hiding this comment.
For security purposes, maybe we should still set some expiry for these tokens?
Also, in config/initializers/doorkeeper.rb, we currently have access_token_expires_in 2.hours. Maybe this token expiry should be extended?
momo3404
reviewed
Feb 18, 2026
aaronskiba
commented
Feb 19, 2026
aaronskiba
commented
Feb 19, 2026
Commit 6e7c21c allows us to have a NULL `redirect_uri`. Previously, this value was merely a placeholder due to the NOT NULL constraint.
f8df035 to
047bb92
Compare
Collaborator
Author
With `hash_token_secrets` enabled, `token.plaintext_token` is only available at creation or rotation time and cannot be retrieved later. This change ensures secure handling of API tokens in line with best practices for hashed token storage. - Update `InternalUserAccessTokenService#rotate!` to return `token.plaintext_token` at creation time - Pass the plaintext token from InternalUserAccessTokensController#create to `app/views/devise/registrations/_v2_api_token.html.erb` - In `_v2_api_token.html.erb`, render the plaintext token when available and display a warning to users to copy and store the token securely, as it will not be shown again after leaving or refreshing the page. - Updated all affected Spec files as well. - The `context 'when user is not authenticated' do` test has been updated. It now enables CSRF protection, enabling the test to accurately capture the behaviour that will be captured in production.
Replace the "Regenerate token" link with a real `<button>` for improved accessibility and native disabled styling. The button is disabled when a token is present, preventing users from generating multiple tokens in rapid succession. This change improves user experience and prevents token spamming.
047bb92 to
f63abd1
Compare
momo3404
approved these changes
Feb 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
NOTE:
This PR is based off of the following: DMPRoadmap#3597. However, that codebase uses Bootstrap 5, whereas this codebase uses Bootstrap 3. To fit the changes to our codebase, the commit Update API Access UI to BS3 (revert after BS5 upgrade) was added.
Changes proposed in this PR:
Summary
This PR introduces support for issuing internal, user-scoped v2 API access tokens, managed entirely within the application (first-party tokens) for internal users.
Key Changes
Create internal Doorkeeper app via rake task
bundle exec rake doorkeeper:ensure_internal_appCreate Api::V2::InternalUserAccessTokenService
Add "POST /api/v2/internal_user_access_token" action & route
Adds
Api::V2::InternalUserAccessTokensController#createwith Pundit authorization and routing. Also reuses the existingusers/refresh_token.js.erbresponse to update the UI via JS.@successis read byapp/views/users/refresh_token.js.erb(similar approach asUsersController#refresh_token)Add API v2 section to /users/edit#api-details
app/views/devise/registrations/_api_token.html.erbto include support for the v2 API access token. Existing v0/v1 token support is retained.Api::V2::InternalUserAccessTokenServiceExpose API Access tab to all users / restrict legacy token rendering
which is accessible to everyone.
Add test coverage for internal v2 token generation
Add request specs for InternalUserAccessTokensController
Add service specs for InternalUserAccessTokenService
Add view specs for API token partials
user.can_use_api?