Skip to content

Conversation

@rameerez
Copy link
Owner

Summary

  • Adds public: true option to key_types config that stores plaintext token in metadata for later viewing
  • Solves the UX nightmare where users lose non-revocable key tokens and get permanently locked out
  • Includes comprehensive security safeguards: tokens are ONLY stored when BOTH public: true AND revocable: false

The Problem

Non-revocable keys create a lockout scenario: if a user creates a publishable key, doesn't copy it immediately, and closes the page—they're stuck forever. They can't recover the token (we only store the hash) and can't delete the key to create a new one (it's non-revocable). With limit: 1, they'd be permanently locked out.

The Solution

For publishable keys designed to be embedded in client apps, there's no security benefit to hiding the token—they're meant to be public! (Stripe lets you view publishable keys anytime.) The public: true option stores the token in metadata so users can view it again.

Security

Token storage requires BOTH conditions:

  • public: true in config
  • revocable: false in config

This ensures secret keys are NEVER stored, even if misconfigured. Added 10 dedicated security tests verifying this.

Changes

File Change
lib/api_keys/models/api_key.rb Add public_key_type?, viewable_token methods + storage logic
app/views/api_keys/keys/_key_row.html.erb Add Show/Copy buttons for public keys
lib/api_keys/configuration.rb Document public option in YARD docs
lib/generators/api_keys/templates/initializer.rb Document public option with security warning
README.md Full documentation with problem/solution/security explanation
test/key_types_test.rb 20 new tests (10 functional + 10 security)

Test plan

  • All 145 tests pass (363 assertions)
  • Public keys store and retrieve token correctly
  • Secret keys NEVER store token (tested with multiple scenarios)
  • Revocable keys with public: true do NOT store token
  • Legacy keys without key_type do NOT store token
  • All edge cases tested (nil, empty string key_type, etc.)

🤖 Generated with Claude Code

Non-revocable keys create a UX problem: if users lose the token, they're
locked out forever since they can't delete and recreate the key. This is
especially problematic with limit: 1 configurations.

This adds a `public: true` option for key types that stores the plaintext
token in metadata, allowing users to view it again in the dashboard.

Security: Token storage ONLY happens when BOTH conditions are met:
- public: true is set in the key type configuration
- revocable: false is set (non-revocable keys only)

This double-check ensures secret keys are NEVER stored, even if someone
accidentally sets public: true on them (since secret keys are revocable
by default).

Changes:
- Add public_key_type? and viewable_token methods to ApiKey model
- Store token in metadata during creation for public keys
- Add Show/Copy buttons in dashboard for public keys
- Document public option in configuration, initializer, and README
- Add 20 tests including 10 security tests verifying secret keys
  are never stored under any circumstances

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link
Contributor

claude bot commented Jan 22, 2026

PR Review: Add Public Key Token Storage for Non-Revocable Publishable Keys

Summary

This PR addresses a critical UX issue with non-revocable publishable keys by allowing them to store plaintext tokens for later viewing. The implementation is well-designed with strong security safeguards.

Code Quality ✅

Strengths:

  • Clear separation of concerns with dedicated methods (public_key_type?, viewable_token)
  • Comprehensive test coverage (20 new tests, including 10 dedicated security tests)
  • Excellent documentation in README with problem/solution/security sections
  • Consistent code style following Ruby/Rails conventions
  • Well-structured conditionals with guard clauses

Minor suggestions:

  1. In lib/api_keys/models/api_key.rb:108 - Consider caching the key_type_config result to avoid calling it twice
  2. In app/views/api_keys/keys/_key_row.html.erb:28 - Inline styles should be extracted to CSS classes for better maintainability

Security Analysis 🔒

Excellent security implementation:

  • ✅ Double-check requirement (both public: true AND revocable: false) prevents accidental secret key storage
  • ✅ 10 dedicated security tests covering edge cases (nil, empty string, misconfigured keys)
  • ✅ Clear warnings in documentation and initializer comments
  • ✅ Token storage only happens in generate_token_and_digest after validation

Potential security consideration:

  • The inline JavaScript in the view uses alert() for copy confirmation. Consider using a more modern approach for better UX
  • The onclick attribute with inline JS could be refactored to use data attributes + event listeners for better CSP compatibility

Performance Considerations ⚡

Good:

  • Metadata storage uses existing JSONB column (no additional DB queries)
  • Token retrieval is O(1) with simple hash lookup
  • No impact on non-public keys

Minor concern:

  • The public_key_type? method is called multiple times in the view. Consider storing the result in a local variable

Test Coverage ✅

Outstanding test coverage:

  • 20 new tests (145 total tests, 363 assertions)
  • All critical paths tested
  • Edge cases well covered (nil, empty string, misconfigured keys)
  • Security tests verify tokens are NEVER stored for secret keys
  • Tests verify behavior across multiple key types

Potential Bugs 🐛

None identified - The implementation appears solid. The defensive programming approach with multiple nil checks and guard clauses prevents edge case failures.

API Design 💎

Excellent API design:

  • public_key_type? - Clear, boolean method follows Ruby naming conventions
  • viewable_token - Descriptive name that clearly indicates purpose
  • Configuration keys are intuitive and self-documenting

Documentation 📚

Exceptional documentation:

  • README section is comprehensive with problem/solution/security explanation
  • Code comments explain the why not just the what
  • Initializer template includes security warnings
  • YARD docs updated in configuration.rb

Recommendations

High Priority

None - the PR is production-ready as-is.

Medium Priority

  1. Extract inline styles to CSS classes (app/views/api_keys/keys/_key_row.html.erb:27-31)
  2. Add CSP-friendly JavaScript - Replace inline onclick handlers with data attributes and event listeners
  3. Cache key_type_config in public_key_type? to avoid double lookup

Low Priority

  1. Consider memoizing public_key_type? if called multiple times per request
  2. Add integration/system test for the Show/Copy button functionality
  3. Consider using a more modern UI feedback mechanism instead of alert()

Conclusion

Recommendation: ✅ APPROVE

This is an excellent PR that solves a real UX problem with strong security safeguards. The code quality is high, test coverage is comprehensive, and documentation is exceptional. The minor suggestions above are optional improvements that don't block merging.

Great work on the security-first approach with the double-check requirement and comprehensive security testing!


🤖 Reviewed with Claude Code

- Change "Create new secret key" button to "Create new API key"
- Change "Secret Key" column header to "API Key"
- Change default key name from "Secret key" to show key type or "API key"
- Make token display message conditional for public keys (they can view again)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@rameerez rameerez merged commit 1407c81 into main Jan 22, 2026
6 of 7 checks passed
@rameerez rameerez deleted the feature/public-key-token-storage branch January 22, 2026 22:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants