api_keys 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/README.md +851 -25
  4. data/SECURITY.md +33 -0
  5. data/app/controllers/api_keys/application_controller.rb +58 -10
  6. data/app/controllers/api_keys/keys_controller.rb +77 -23
  7. data/app/controllers/api_keys/security_controller.rb +8 -0
  8. data/app/views/api_keys/keys/_empty_state.html.erb +9 -0
  9. data/app/views/api_keys/keys/_form.html.erb +33 -4
  10. data/app/views/api_keys/keys/_key_actions.html.erb +20 -0
  11. data/app/views/api_keys/keys/_key_badges.html.erb +17 -0
  12. data/app/views/api_keys/keys/_key_row.html.erb +21 -35
  13. data/app/views/api_keys/keys/_key_status.html.erb +10 -0
  14. data/app/views/api_keys/keys/_keys_table.html.erb +3 -11
  15. data/app/views/api_keys/keys/_publishable_keys.html.erb +40 -0
  16. data/app/views/api_keys/keys/_secret_keys.html.erb +39 -0
  17. data/app/views/api_keys/keys/_show_token.html.erb +10 -47
  18. data/app/views/api_keys/keys/_token_display.html.erb +11 -0
  19. data/app/views/api_keys/keys/index.html.erb +40 -8
  20. data/app/views/api_keys/keys/show.html.erb +2 -2
  21. data/app/views/api_keys/security/best_practices.html.erb +73 -47
  22. data/app/views/layouts/api_keys/application.html.erb +267 -14
  23. data/lib/api_keys/authentication.rb +39 -11
  24. data/lib/api_keys/configuration.rb +444 -17
  25. data/lib/api_keys/engine.rb +5 -20
  26. data/lib/api_keys/errors.rb +73 -0
  27. data/lib/api_keys/form_builder_extensions.rb +168 -0
  28. data/lib/api_keys/helpers/expiration_options.rb +139 -0
  29. data/lib/api_keys/helpers/token_session.rb +203 -0
  30. data/lib/api_keys/helpers/view_helpers.rb +220 -0
  31. data/lib/api_keys/jobs/callbacks_job.rb +10 -17
  32. data/lib/api_keys/jobs/update_stats_job.rb +27 -12
  33. data/lib/api_keys/models/api_key.rb +452 -21
  34. data/lib/api_keys/models/concerns/has_api_keys.rb +269 -26
  35. data/lib/api_keys/services/authenticator.rb +300 -112
  36. data/lib/api_keys/services/digestor.rb +81 -14
  37. data/lib/api_keys/services/token_generator.rb +41 -1
  38. data/lib/api_keys/tenant_resolution.rb +4 -4
  39. data/lib/api_keys/version.rb +1 -1
  40. data/lib/api_keys.rb +12 -0
  41. data/lib/generators/api_keys/add_authentication_index_generator.rb +36 -0
  42. data/lib/generators/api_keys/add_key_types_generator.rb +68 -0
  43. data/lib/generators/api_keys/templates/add_authentication_index_to_api_keys.rb.erb +32 -0
  44. data/lib/generators/api_keys/templates/add_key_types_to_api_keys.rb.erb +18 -0
  45. data/lib/generators/api_keys/templates/create_api_keys_table.rb.erb +11 -3
  46. data/lib/generators/api_keys/templates/initializer.rb +261 -120
  47. metadata +29 -63
  48. data/Rakefile +0 -32
@@ -0,0 +1,39 @@
1
+ <%# Partial for displaying secret keys section %>
2
+ <%# Locals: active_keys (Active secret keys), inactive_keys (Inactive secret keys) %>
3
+
4
+ <section class="api-keys-section api-keys-secret-section" aria-labelledby="secret-keys-heading">
5
+ <h2 id="secret-keys-heading">Secret Keys</h2>
6
+ <p class="api-keys-section-description">
7
+ Keep these private. Never expose in client-side code or share publicly.
8
+ </p>
9
+
10
+ <div class="api-keys-table-wrapper">
11
+ <% all_keys = active_keys + inactive_keys %>
12
+ <% if all_keys.any? %>
13
+ <table>
14
+ <thead>
15
+ <tr>
16
+ <th>Name</th>
17
+ <th>API Key</th>
18
+ <th>Created</th>
19
+ <th>Expires</th>
20
+ <th>Last Used</th>
21
+ <th>Permissions</th>
22
+ <th>Actions</th>
23
+ </tr>
24
+ </thead>
25
+ <tbody>
26
+ <% active_keys.each do |key| %>
27
+ <%= render partial: 'api_keys/keys/key_row', locals: { key: key } %>
28
+ <% end %>
29
+
30
+ <% inactive_keys.each do |key| %>
31
+ <%= render partial: 'api_keys/keys/key_row', locals: { key: key, inactive: true } %>
32
+ <% end %>
33
+ </tbody>
34
+ </table>
35
+ <% else %>
36
+ <%= render partial: 'api_keys/keys/empty_state', locals: { message: "No secret keys yet." } %>
37
+ <% end %>
38
+ </div>
39
+ </section>
@@ -3,25 +3,29 @@
3
3
 
4
4
  <h2>Save your key</h2>
5
5
 
6
- <p>Please save your secret key in a safe place since <strong>you won't be able to view it again</strong>. Keep it secure, as anyone with your API key can make requests on your behalf. If you do lose it, you'll need to generate a new one.</p>
6
+ <% if api_key.public_key_type? %>
7
+ <p>Here's your <%= api_key.key_type.humanize.downcase %> key. This key is designed to be embedded in client-side applications. You can view it again anytime from your dashboard.</p>
8
+ <% else %>
9
+ <p>Please save your API key in a safe place since <strong>you won't be able to view it again</strong>. Keep it secure, as anyone with your API key can make requests on your behalf. If you lose it, you'll need to generate a new one.</p>
10
+ <% end %>
7
11
 
8
12
  <p>
9
- <%= link_to api_keys.security_best_practices_path, class: "text-primary api-keys-align-center" do %>
13
+ <%= link_to security_best_practices_path, class: "text-primary api-keys-align-center" do %>
10
14
  Learn more about API key best practices&nbsp;
11
15
  <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M15 5a1 1 0 1 1 0-2h5a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0V6.414l-5.293 5.293a1 1 0 0 1-1.414-1.414L17.586 5H15ZM4 7a3 3 0 0 1 3-3h3a1 1 0 1 1 0 2H7a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-3a1 1 0 1 1 2 0v3a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3V7Z" clip-rule="evenodd"></path></svg>
12
16
  <% end %>
13
17
  </p>
14
18
 
15
19
 
16
- <div style="padding: 1em; margin: 1em 0; border-radius: 4px;">
20
+ <div class="api-keys-token-panel">
17
21
  <div class="card bd-primary">
18
22
  <div class="row">
19
23
  <div class="col-7 is-vertical-align is-center">
20
- <pre id="api-key-token" style="word-wrap: break-word;"><%= plaintext_token %></pre>
24
+ <pre id="api-key-token" class="api-keys-token-value"><%= plaintext_token %></pre>
21
25
  </div>
22
26
 
23
27
  <div class="col is-vertical-align is-center">
24
- <button id="copy-api-key-button" onclick="copyTokenToClipboard()" class="button primary api-keys-align-center">
28
+ <button id="copy-api-key-button" type="button" class="button primary api-keys-align-center btn-copy-new-token">
25
29
  <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M7 5a3 3 0 0 1 3-3h9a3 3 0 0 1 3 3v9a3 3 0 0 1-3 3h-2v2a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3v-9a3 3 0 0 1 3-3h2V5Zm2 2h5a3 3 0 0 1 3 3v5h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1h-9a1 1 0 0 0-1 1v2ZM5 9a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1v-9a1 1 0 0 0-1-1H5Z" clip-rule="evenodd"></path></svg>
26
30
  <span class="api-keys-button-text">Copy</span>
27
31
  </button>
@@ -32,7 +36,7 @@
32
36
 
33
37
 
34
38
  <% if api_key.scopes.present? %>
35
- <div style="margin-top: 2em;">
39
+ <div class="api-keys-token-scopes">
36
40
  <p><strong>Permissions</strong></p>
37
41
 
38
42
  <% if api_key.scopes.present? %>
@@ -45,44 +49,3 @@
45
49
  </div>
46
50
  <% end %>
47
51
  </div>
48
-
49
- <%# Simple JavaScript for copy-to-clipboard functionality %>
50
- <%# Ensure this script is loaded only once if rendering multiple components %>
51
- <script>
52
- function copyTokenToClipboard() {
53
- const tokenElement = document.getElementById('api-key-token');
54
- const copyButton = document.getElementById('copy-api-key-button');
55
- const buttonTextElement = copyButton.querySelector('.api-keys-button-text'); // Target the span containing the text
56
- const originalButtonText = buttonTextElement.innerHTML; // Store original HTML
57
-
58
- if (navigator.clipboard && tokenElement && copyButton && buttonTextElement) {
59
- navigator.clipboard.writeText(tokenElement.textContent || '').then(() => {
60
- buttonTextElement.innerHTML = 'Copied!'; // Update text
61
- copyButton.classList.add('success'); // Optional: Add class for styling
62
- setTimeout(() => {
63
- buttonTextElement.innerHTML = originalButtonText; // Revert text
64
- copyButton.classList.remove('success'); // Optional: Remove class
65
- }, 2000);
66
- }).catch(err => {
67
- buttonTextElement.innerHTML = 'Failed'; // Update text on error
68
- copyButton.classList.add('error'); // Optional: Add class for styling
69
- console.error('Failed to copy text: ', err);
70
- setTimeout(() => {
71
- buttonTextElement.innerHTML = originalButtonText; // Revert text
72
- copyButton.classList.remove('error'); // Optional: Remove class
73
- }, 2000);
74
- });
75
- } else {
76
- // Fallback or indicate unavailability more clearly if needed
77
- buttonTextElement.innerHTML = 'Cannot Copy';
78
- copyButton.classList.add('error'); // Optional: Add class for styling
79
- console.warn('Clipboard API not available or element missing.');
80
- setTimeout(() => {
81
- buttonTextElement.innerHTML = originalButtonText; // Revert text
82
- copyButton.classList.remove('error'); // Optional: Remove class
83
- }, 2000);
84
- }
85
- }
86
- // Automatically try to copy when the partial is rendered, if desired?
87
- // document.addEventListener('DOMContentLoaded', copyTokenToClipboard); // Example
88
- </script>
@@ -0,0 +1,11 @@
1
+ <%# Partial for displaying an API key token with optional show/copy functionality %>
2
+ <%# Locals: key (required) - The ApiKey record %>
3
+
4
+ <% if key.public_key_type? && key.viewable_token.present? %>
5
+ <span class="token-masked"><code><%= key.masked_token %></code></span>
6
+ <span class="token-full" hidden><code class="api-keys-token-break"><%= key.viewable_token %></code></span>
7
+ <button type="button" class="btn-show-token" title="Show full token">Show</button>
8
+ <button type="button" class="btn-copy-token" title="Copy to clipboard" data-token="<%= key.viewable_token %>" hidden>Copy</button>
9
+ <% else %>
10
+ <code><%= key.masked_token %></code>
11
+ <% end %>
@@ -7,7 +7,7 @@
7
7
  <div class="col api-keys-align-center is-right">
8
8
  <%= link_to new_key_path, class: "button primary api-keys-align-center", role: "button" do %>
9
9
  <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M12 5a1 1 0 0 1 1 1v5h5a1 1 0 1 1 0 2h-5v5a1 1 0 1 1-2 0v-5H6a1 1 0 1 1 0-2h5V6a1 1 0 0 1 1-1Z" clip-rule="evenodd"></path></svg>
10
- <span>&nbsp;Create new secret key</span>
10
+ <span>&nbsp;Create new API key</span>
11
11
  <% end %>
12
12
  </div>
13
13
 
@@ -15,12 +15,44 @@
15
15
 
16
16
  <div>
17
17
 
18
- <p>Do not share your API key with others or expose it in the browser or other client-side code. <%= link_to api_keys.security_best_practices_path, class: "text-primary api-keys-align-center" do %>
19
- Learn more&nbsp;
20
- <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M15 5a1 1 0 1 1 0-2h5a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0V6.414l-5.293 5.293a1 1 0 0 1-1.414-1.414L17.586 5H15ZM4 7a3 3 0 0 1 3-3h3a1 1 0 1 1 0 2H7a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-3a1 1 0 1 1 2 0v3a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3V7Z" clip-rule="evenodd"></path></svg>
21
- <% end %>
18
+ <% if key_types_feature_enabled? %>
19
+ <% has_publishable_keys = @publishable_keys.any? || @inactive_publishable_keys.any? %>
20
+
21
+ <p class="api-keys-info-text">
22
+ <% if has_publishable_keys %>
23
+ <strong>Secret keys</strong> should never be shared or exposed publicly.
24
+ <strong>Publishable keys</strong> can be safely embedded in client applications.
25
+ <% else %>
26
+ Do not share your API key with others or expose it in the browser or other client-side code.
27
+ <% end %>
28
+ <%= link_to security_best_practices_path, class: "text-primary api-keys-align-center" do %>
29
+ Learn more&nbsp;
30
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M15 5a1 1 0 1 1 0-2h5a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0V6.414l-5.293 5.293a1 1 0 0 1-1.414-1.414L17.586 5H15ZM4 7a3 3 0 0 1 3-3h3a1 1 0 1 1 0 2H7a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-3a1 1 0 1 1 2 0v3a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3V7Z" clip-rule="evenodd"></path></svg>
31
+ <% end %>
32
+ </p>
22
33
 
23
- <%# Render the reusable table partial %>
24
- <%= render partial: 'keys_table', locals: { active_keys: @api_keys, inactive_keys: @inactive_api_keys } %>
34
+ <%# Render secret keys section first (primary use case) %>
35
+ <%= render partial: 'secret_keys', locals: {
36
+ active_keys: @secret_keys,
37
+ inactive_keys: @inactive_secret_keys
38
+ } %>
39
+
40
+ <%# Only render publishable keys section if there are any %>
41
+ <% if has_publishable_keys %>
42
+ <%= render partial: 'publishable_keys', locals: {
43
+ active_keys: @publishable_keys,
44
+ inactive_keys: @inactive_publishable_keys
45
+ } %>
46
+ <% end %>
47
+
48
+ <% else %>
49
+ <p>Do not share your API key with others or expose it in the browser or other client-side code. <%= link_to security_best_practices_path, class: "text-primary api-keys-align-center" do %>
50
+ Learn more&nbsp;
51
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M15 5a1 1 0 1 1 0-2h5a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0V6.414l-5.293 5.293a1 1 0 0 1-1.414-1.414L17.586 5H15ZM4 7a3 3 0 0 1 3-3h3a1 1 0 1 1 0 2H7a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-3a1 1 0 1 1 2 0v3a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3V7Z" clip-rule="evenodd"></path></svg>
52
+ <% end %>
53
+
54
+ <%# Render the reusable table partial (legacy mode - single table) %>
55
+ <%= render partial: 'keys_table', locals: { active_keys: @api_keys, inactive_keys: @inactive_api_keys } %>
56
+ <% end %>
25
57
 
26
- </div>
58
+ </div>
@@ -4,9 +4,9 @@
4
4
  <%= render partial: 'show_token', locals: { api_key: @api_key, plaintext_token: @plaintext_token } %>
5
5
 
6
6
  <%# Add navigation links %>
7
- <p style="margin-top: 2em;">
7
+ <p class="api-keys-show-footer">
8
8
  <%= link_to ApiKeys.configuration.return_text, ApiKeys.configuration.return_url, class: "button outline" %>
9
9
  <%= link_to "All API Keys", keys_path, class: "button" %>
10
10
  </p>
11
11
 
12
- </div>
12
+ </div>
@@ -1,70 +1,96 @@
1
1
  <header>
2
- <h1>API Key Security Best Practices</h1>
3
- <p>Protecting your API keys is crucial for maintaining the security and integrity of your account and data.</p>
2
+ <h1>API Key Security</h1>
3
+ <p>Protecting your API keys is crucial for maintaining the security of your account and data.</p>
4
4
  </header>
5
5
 
6
- <article class="col-5">
6
+ <article class="col-8">
7
7
 
8
- <section>
9
- <h3>1. Treat API Keys Like Passwords</h3>
10
- <p>Your API keys grant access to your account and potentially sensitive operations. Handle them with the same level of security you would apply to your account password or other critical credentials.</p>
11
- </section>
8
+ <% if key_types_feature_enabled? %>
9
+ <section class="api-keys-section">
10
+ <h2>Understanding Key Types</h2>
12
11
 
13
- <section>
14
- <h3>2. Use Unique Keys for Different Applications & Environments</h3>
15
- <p>Generate distinct API keys for different applications, services, or integrations that need access. If a key for one application is compromised, you can revoke it without disrupting others. Use separate keys for development, staging, and production environments.</p>
16
- <p><em>Tip:</em> Use the "Name" field when creating keys to easily identify their purpose (e.g., "Production Zapier Integration", "Staging iOS App").</p>
17
- </section>
12
+ <h3>Secret Keys</h3>
13
+ <p>Secret keys may grant sensitive or broad access, depending on their configured permissions, and must be treated like passwords.</p>
14
+ <ul>
15
+ <li><strong>Never expose</strong> in client-side code (browsers, mobile apps, desktop apps)</li>
16
+ <li><strong>Never commit</strong> to version control (Git, etc.)</li>
17
+ <li><strong>Store securely</strong> using environment variables or secrets management</li>
18
+ <li><strong>Can be revoked</strong> immediately if compromised</li>
19
+ </ul>
18
20
 
19
- <section>
20
- <h3>3. Never Expose Keys in Client-Side Code</h3>
21
- <p><strong>Never</strong> embed API keys directly in mobile apps (iOS, Android), browser-side JavaScript, desktop applications, or any code that resides on a user's device. Exposed keys can be easily extracted by malicious actors.</p>
22
- <p><strong>Solution:</strong> Route API requests through your own backend server. Your server can securely store and use the API key to communicate with the target API on behalf of the client.</p>
23
- </section>
21
+ <h3>Publishable Keys</h3>
22
+ <p>Publishable keys are intentionally exposed identifiers. They are suitable for client-side use <strong>only when every configured permission is safe for an untrusted public client</strong>.</p>
23
+ <ul>
24
+ <li><strong>Assume anyone can copy and abuse them</strong> from browser JavaScript, mobile apps, or public code</li>
25
+ <li><strong>Grant only public-safe operations</strong> and enforce authorization server-side for every permission</li>
26
+ <li><strong>Always visible</strong> &mdash; you can view the full key anytime in your dashboard</li>
27
+ <li><strong>Cannot be revoked individually</strong> &mdash; maintain an application-level disable/replacement procedure</li>
28
+ </ul>
29
+ </section>
30
+ <% end %>
24
31
 
25
32
  <section>
26
- <h3>4. Never Commit Keys to Version Control (e.g., Git)</h3>
27
- <p>Committing keys to your source code repository (like Git, Mercurial, etc.) is a common and dangerous mistake. Even in private repositories, accidental pushes or repository breaches can leak your keys.</p>
28
- <p><strong>Solution:</strong> Store keys in environment variables or use a dedicated secrets management system. Access the key in your code via these secure methods.</p>
29
- </section>
33
+ <h2>Essential Practices</h2>
30
34
 
31
- <section>
32
- <h3>5. Securely Store Keys on Your Backend</h3>
33
- <ul>
34
- <li><strong>Environment Variables:</strong> The simplest secure method for many applications. Set an environment variable (e.g., `YOUR_SERVICE_API_KEY`) on your server and access it in your code (e.g., `ENV['YOUR_SERVICE_API_KEY']` in Ruby/Rails).</li>
35
- <li><strong>Secrets Management Services:</strong> For more robust needs, especially in production or team environments, use dedicated services like HashiCorp Vault, AWS Secrets Manager, Google Secret Manager, Doppler, etc. These provide encrypted storage, access control, auditing, and often automated rotation capabilities.</li>
36
- <li><strong>Encrypted Configuration Files:</strong> If using configuration files, ensure they are encrypted (e.g., Rails encrypted credentials `config/credentials.yml.enc` and `Rails.application.credentials`). <%= link_to "More info here", "https://guides.rubyonrails.org/security.html#custom-credentials", target: "_blank", rel: "noopener noreferrer", class: "text-primary" %>.</li>
37
- </ul>
35
+ <h3>Treat Secret Keys Like Passwords</h3>
36
+ <p>Your API keys grant access to your account. Handle them with the same care you would apply to your account password.</p>
37
+
38
+ <h3>Use Separate Keys for Different Purposes</h3>
39
+ <p>Create distinct keys for different applications and environments. If one key is compromised, you can revoke it without disrupting others.</p>
40
+ <p><em>Tip:</em> Use descriptive names like "Production Backend" or "Staging iOS App" to easily identify each key's purpose.</p>
41
+
42
+ <% unless key_types_feature_enabled? %>
43
+ <h3>Never Expose Keys in Client-Side Code</h3>
44
+ <p><strong>Never</strong> embed API keys in mobile apps, browser JavaScript, or desktop applications. Exposed keys can be easily extracted by malicious actors.</p>
45
+ <p><strong>Solution:</strong> Route API requests through your own backend server, which can securely store and use the API key.</p>
46
+ <% end %>
38
47
  </section>
39
48
 
40
49
  <section>
41
- <h3>6. Implement the Principle of Least Privilege (Scopes)</h3>
42
- <p>If the API service supports it (and this `api_keys` gem allows for scopes), create keys with only the minimum permissions (scopes) required for their specific task. Avoid using a key with full access if only read access is needed.</p>
43
- <p><em>Note:</em> Scope availability and enforcement depend on how the host application integrates and utilizes the `scopes` attribute provided by this gem.</p>
50
+ <h2>Secure Storage</h2>
51
+
52
+ <h3>Environment Variables</h3>
53
+ <p>The simplest secure method. Set an environment variable on your server:</p>
54
+ <pre><code># In your shell or deployment config
55
+ export YOUR_SERVICE_API_KEY="sk_..."
56
+
57
+ # Access in Ruby
58
+ ENV['YOUR_SERVICE_API_KEY']</code></pre>
59
+
60
+ <h3>Rails Encrypted Credentials</h3>
61
+ <p>For Rails applications, use encrypted credentials:</p>
62
+ <pre><code># Edit credentials
63
+ bin/rails credentials:edit
64
+
65
+ # Access in code
66
+ Rails.application.credentials.your_service_api_key</code></pre>
67
+ <p><%= link_to "Rails Security Guide", "https://guides.rubyonrails.org/security.html#custom-credentials", target: "_blank", rel: "noopener noreferrer", class: "text-primary" %></p>
68
+
69
+ <h3>Secrets Management Services</h3>
70
+ <p>For production environments, consider dedicated services like HashiCorp Vault, AWS Secrets Manager, or Google Secret Manager.</p>
44
71
  </section>
45
72
 
46
73
  <section>
47
- <h3>7. Monitor Usage and Rotate Keys Regularly</h3>
48
- <ul>
49
- <li><strong>Monitor Usage:</strong> Regularly check API usage logs or dashboards (if provided by the service or your monitoring tools). Look for unexpected spikes in activity or requests from unusual locations, which could indicate a compromised key.</li>
50
- <li><strong>Rotate Keys:</strong> Periodically generate new keys and revoke old ones (key rotation). This limits the window of opportunity for attackers if a key is ever leaked undetected. How often you rotate depends on your security requirements (e.g., every 90 days, annually).
51
- <br><em>Tip:</em> This dashboard allows creating multiple keys, facilitating rotation. Create a new key, update your application(s), verify they work, and then revoke the old key.</li>
52
- <li><strong>Revoke Immediately if Compromised:</strong> If you suspect a key has been leaked or compromised, revoke it immediately using the "Revoke" button on your keys dashboard.</li>
53
- </ul>
74
+ <h2>Monitoring &amp; Rotation</h2>
75
+
76
+ <h3>Monitor Usage</h3>
77
+ <p>Regularly check for unexpected activity spikes or requests from unusual locations, which could indicate a compromised key.</p>
78
+
79
+ <h3>Rotate Secret Keys Periodically</h3>
80
+ <p>Generate new keys and revoke old ones on a regular schedule (e.g., every 90 days). This limits exposure if a key is leaked undetected.</p>
81
+ <p><em>Tip:</em> Create a new key first, update your application, verify it works, then revoke the old key.</p>
82
+
83
+ <h3>Revoke Immediately if Compromised</h3>
84
+ <p>If you suspect a key has been leaked, revoke it immediately from your dashboard.</p>
54
85
  </section>
55
86
 
56
87
  <section>
57
- <h3>8. Use HTTPS Exclusively</h3>
58
- <p>Ensure all API requests are made over HTTPS to encrypt the connection and prevent eavesdropping. Transmitting keys over unencrypted HTTP is highly insecure.</p>
88
+ <h2>Always Use HTTPS</h2>
89
+ <p>Ensure all API requests are made over HTTPS. Transmitting keys over unencrypted HTTP exposes them to eavesdropping.</p>
59
90
  </section>
60
91
 
61
92
  <hr>
62
93
 
63
- <p>By following these best practices, you significantly reduce the risk associated with API key management.</p>
64
-
65
- <%# Link back to the keys index if appropriate %>
66
- <% if defined?(api_keys.keys_path) %>
67
- <p><%= link_to "Back to API Keys", api_keys.keys_path, class: "text-primary" %></p>
68
- <% end %>
94
+ <p><%= link_to "Back to API Keys", keys_path, class: "text-primary" %></p>
69
95
 
70
- </article>
96
+ </article>