Creating Signed URLs in WordPress: Using Nonces and Custom HMAC
When you need to create temporary or restricted access to content in WordPress, you have two primary options: WordPress nonces and custom signed URLs. This comprehensive guide will show you when and how to use each approach.
This comprehensive blog post combines both techniques with:
-
Clear explanations of both nonces and signed URLs
-
Detailed implementation guides for each method
-
Comparison table highlighting when to use each approach
-
Visual styling for better readability
-
Security best practices for both methods
-
A hybrid approach for cases where both might be needed
-
Practical code examples ready for implementation
The post is structured to help WordPress developers:
-
Understand the differences between the approaches
-
Implement either solution with confidence
-
Choose the right method for their specific use case
-
Follow security best practices
Understanding the Options
- WordPress Nonces: Built-in security tokens tied to user sessions and specific actions
- Signed URLs: Custom URLs with cryptographic signatures and explicit expiration
WordPress Nonces
Nonces (“number used once”) are WordPress’s built-in security mechanism for verifying the intent behind requests. They’re primarily used for:
- CSRF (Cross-Site Request Forgery) protection
- Securing forms and admin actions
- Creating temporary links for authenticated users
Signed URLs
Signed URLs are custom URLs that contain:
- A cryptographic signature
- An expiration timestamp
- Optional access parameters
They’re useful for:
- Sharing private files temporarily
- Granting access to non-WordPress users
- Creating time-limited download links
Comparison Table
Feature | WordPress Nonce | Signed URL |
---|---|---|
Implementation | Built-in WordPress functions | Custom code required |
User Context | Tied to user sessions | Can be user-agnostic |
Expiration | 24 hours default (configurable) | Precise timestamp control |
Security Level | Good for WordPress actions | Higher (cryptographic) |
Best For | Forms, admin actions, logged-in users | File sharing, public access, API endpoints |
Implementing WordPress Nonces
1. Creating a Nonce URL
<code class="language-php"><?php // Generate nonce for specific action $nonce = wp_create_nonce('view-protected-content'); // Add to URL $protected_url = add_query_arg('_wpnonce', $nonce, get_permalink($protected_page_id) ); ?></code>
2. Verifying the Nonce
<code class="language-php"><?php if (!isset($_GET['_wpnonce']) || !wp_verify_nonce($_GET['_wpnonce'], 'view-protected-content')) { wp_die('Security check failed', 'Error', 403); } // Show protected content ?></code>
3. Advanced Nonce Customization
<code class="language-php"><?php // Change nonce lifetime to 2 hours add_filter('nonce_life', function() { return 7200; }); // Create nonce for specific user $user_id = 123; $nonce = wp_create_nonce('user-access-' . $user_id); ?></code>
Implementing Custom Signed URLs
1. Generating Signed URLs
<code class="language-php"><?php function generate_signed_url($base_url, $expires, $secret_key) { $url = add_query_arg('expires', $expires, $base_url); $signature = hash_hmac('sha256', $url, $secret_key); return add_query_arg('signature', $signature, $url); } // Usage: $signed_url = generate_signed_url( 'https://yoursite.com/protected-file.pdf', time() + 3600, // 1 hour expiration 'your-secret-key' ); ?></code>
2. Validating Signed URLs
<code class="language-php"><?php function validate_signed_url($secret_key) { $expires = $_GET['expires'] ?? 0; $signature = $_GET['signature'] ?? ''; $request_url = remove_query_arg('signature'); $request_url = add_query_arg('expires', $expires, $request_url); if (time() > $expires) return false; $expected = hash_hmac('sha256', $request_url, $secret_key); return hash_equals($signature, $expected); } // Usage: if (!validate_signed_url('your-secret-key')) { wp_die('Invalid or expired URL', 403); } ?></code>
3. Enhancing Signed URLs
<code><?php // Add IP restriction function generate_restricted_url($base_url, $expires, $secret_key) { $url = add_query_arg([ 'expires' => $expires, 'allowed_ip' => $_SERVER['REMOTE_ADDR'] ], $base_url); $signature = hash_hmac('sha256', $url, $secret_key); return add_query_arg('signature', $signature, $url); } // Validate with IP check function validate_restricted_url($secret_key) { // ... existing validation ... if ($_GET['allowed_ip'] !== $_SERVER['REMOTE_ADDR']) { return false; } return true; } ?></code>
When to Use Each Technique
Use WordPress Nonces When:
- Protecting WordPress admin actions or forms
- The access is tied to a WordPress user session
- You need quick implementation without custom code
- Default 24-hour expiration is acceptable
Use Signed URLs When:
- Sharing content with non-WordPress users
- You need precise control over expiration time
- The access isn’t tied to WordPress sessions
- You need cryptographic security beyond nonces
- Creating time-limited download links
Security Best Practices
- Always use HTTPS
- Store secret keys securely (wp-config.php)
- Set reasonable expiration times
- Log access attempts for sensitive content
Nonce-Specific:
- Use unique action names for each nonce
- Don’t expose nonces to untrusted users
Signed URL-Specific:
- Rotate secret keys periodically
- Consider adding IP restrictions for sensitive content
- Use strong hashing algorithms (SHA-256+)
Hybrid Approach
For some use cases, you might combine both techniques:
<code><?php // Generate URL with both nonce and signature function generate_hybrid_url($user_id, $base_url, $expires, $secret_key) { // Add WordPress nonce $nonce = wp_create_nonce('hybrid-access-' . $user_id); $url = add_query_arg('_wpnonce', $nonce, $base_url); // Add signed URL parameters $url = add_query_arg('expires', $expires, $url); $signature = hash_hmac('sha256', $url, $secret_key); return add_query_arg('signature', $signature, $url); } // Validate both function validate_hybrid_url($user_id, $secret_key) { // Verify nonce first if (!wp_verify_nonce($_GET['_wpnonce'], 'hybrid-access-' . $user_id)) { return false; } // Then verify signature return validate_signed_url($secret_key); } ?></code>
Conclusion
Both WordPress nonces and custom signed URLs have important roles in WordPress security:
- For WordPress-native functionality (forms, admin actions), nonces are the simpler and more appropriate choice.
- For public-facing, time-limited content sharing, signed URLs provide more flexibility and security.
- For maximum security in critical systems, consider combining both approaches.
By understanding both techniques, you can choose the right tool for each security challenge in your WordPress projects.