Table of Contents#
- Understanding the Problem: Why Authorization Headers Fail in AngularJS
- What is CORS Preflight, and Why Does It Matter?
- Why AngularJS Triggers Preflight for Authorization Headers
- Step-by-Step Fixes: Resolving CORS Preflight Issues
- Common Pitfalls to Avoid
- Troubleshooting: Debugging Preflight Failures
- Conclusion
- References
1. Understanding the Problem: Why Authorization Headers Fail in AngularJS#
Let’s start with a typical scenario:
You’re using AngularJS’s $http service to send a request to your backend API with an authorization token. Your code looks like this:
// AngularJS controller code
$http({
method: 'GET',
url: 'https://api.yourbackend.com/data',
headers: {
'Authorization': 'Bearer YOUR_JWT_TOKEN' // Token added here
}
}).then(function(response) {
console.log('Success:', response.data);
}, function(error) {
console.error('Error:', error); // Fails with 401/403 or CORS error
});But the request fails. Checking the browser’s Network tab, you notice:
- No
GETrequest tohttps://api.yourbackend.com/dataappears. - Instead, there’s an
OPTIONSrequest (the preflight) that returns a403 Forbiddenor405 Method Not Allowed.
Why isn’t the Authorization header being sent? The answer lies in how browsers handle cross-origin requests with custom headers like Authorization.
2. What is CORS Preflight, and Why Does It Matter?#
CORS is a browser security feature that restricts cross-origin HTTP requests initiated from scripts (like AngularJS). A "cross-origin" request is one where the frontend (e.g., http://localhost:8080) and backend (e.g., https://api.yourbackend.com) have different domains, ports, or protocols.
For simple requests (e.g., GET/POST with standard headers like Content-Type: application/x-www-form-urlencoded), browsers send the request directly. But for non-simple requests, the browser first sends a preflight request (using the OPTIONS method) to the backend to "ask permission" before sending the actual request.
When is a Request "Non-Simple"?#
A request triggers preflight if it:
- Uses methods other than
GET,POST, orHEAD. - Includes custom headers (e.g.,
Authorization,X-Custom-Header). - Has a
Content-Typeofapplication/json,multipart/form-data, orapplication/xml(non-standard for forms).
Since Authorization is a custom header, your AngularJS request is non-simple—and thus triggers a preflight OPTIONS request.
What Happens During Preflight?#
The browser sends an OPTIONS request to the backend with headers like:
Origin: The frontend’s origin (e.g.,http://localhost:8080).Access-Control-Request-Method: The method of the actual request (e.g.,GET).Access-Control-Request-Headers: The custom headers in the actual request (e.g.,Authorization).
The backend must respond to this OPTIONS request with headers that explicitly allow the cross-origin request. If the backend rejects the preflight (e.g., missing CORS headers), the browser blocks the actual request—and your Authorization header is never sent.
3. Why AngularJS Triggers Preflight for Authorization Headers#
AngularJS itself doesn’t "trigger" preflight—it’s the browser’s behavior. However, AngularJS’s $http service often leads to non-simple requests in two common ways:
- Custom Headers: Adding
Authorization: Bearer ...explicitly makes the request non-simple. - Default
Content-Type: AngularJS setsContent-Type: application/jsonfor requests with a JSON body, which is also non-simple.
Even if you’re not using JSON, the Authorization header alone is enough to trigger preflight.
4. Step-by-Step Fixes: Resolving CORS Preflight Issues#
To fix the Authorization header issue, you need to address two key areas:
- Backend Server Configuration: Ensure the server handles preflight
OPTIONSrequests and returns the correct CORS headers. - AngularJS Configuration: Ensure the
Authorizationheader is set consistently (e.g., via interceptors).
4.1 Configure the Server to Handle OPTIONS Requests#
The first step is ensuring your backend accepts OPTIONS requests. Many servers block OPTIONS by default (e.g., Apache, Nginx, or Express.js with strict routing).
Example 1: Node.js/Express.js#
If you’re using Express, add middleware to handle OPTIONS requests and enable CORS:
// Install cors package: npm install cors
const express = require('express');
const cors = require('cors');
const app = express();
// Configure CORS
app.use(cors({
origin: 'http://localhost:8080', // Replace with your frontend origin
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // Allow required methods
allowedHeaders: ['Content-Type', 'Authorization'], // Include Authorization
credentials: true // If using cookies (optional)
}));
// Handle preflight OPTIONS requests for all routes
app.options('*', cors()); // Respond to OPTIONS with CORS headers
// Your API routes (e.g., GET /data)
app.get('/data', (req, res) => {
// Verify Authorization header here
const token = req.headers.authorization;
if (!token) return res.status(401).send('Unauthorized');
res.json({ message: 'Data received!' });
});
app.listen(3000, () => console.log('Backend running on port 3000'));Example 2: Apache Server#
For Apache, enable CORS by adding this to your .htaccess or httpd.conf:
# Enable OPTIONS method
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]
</IfModule>
# Set CORS headers
Header set Access-Control-Allow-Origin "http://localhost:8080"
Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header set Access-Control-Allow-Headers "Content-Type, Authorization"
Header set Access-Control-Max-Age "86400" # Cache preflight response for 24hExample 3: Nginx Server#
For Nginx, update your nginx.conf or site configuration:
server {
listen 80;
server_name api.yourbackend.com;
# Handle OPTIONS requests
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "http://localhost:8080";
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Access-Control-Max-Age 86400; # Cache for 24h
return 204; # No content for preflight
}
# For other requests, add CORS headers
location / {
add_header Access-Control-Allow-Origin "http://localhost:8080";
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
proxy_pass http://your-backend-server;
}
}4.2 Set Required CORS Response Headers#
The preflight OPTIONS request must return specific headers to allow the actual request. Here are the critical ones:
| Header | Purpose |
|---|---|
Access-Control-Allow-Origin | Specifies which frontend origins are allowed (e.g., http://localhost:8080; avoid * if using credentials). |
Access-Control-Allow-Methods | Lists HTTP methods allowed (e.g., GET, POST, OPTIONS). |
Access-Control-Allow-Headers | Includes custom headers used in the actual request (must include Authorization). |
Access-Control-Max-Age | Caches the preflight response (e.g., 86400 = 24 hours) to reduce overhead. |
Critical Note: If your frontend and backend use credentials (e.g., cookies), Access-Control-Allow-Origin cannot be *—it must explicitly list the frontend origin (e.g., http://localhost:8080).
4.3 AngularJS $http Configuration: Sending the Authorization Header#
Now that the backend is configured, ensure AngularJS consistently sends the Authorization header. The best way is to use an interceptor to add the header to all requests globally (instead of repeating it in every $http call).
Step 1: Create an Authorization Interceptor#
An interceptor is a middleware that modifies requests/responses before they’re sent or processed. Here’s how to set one up:
// AngularJS module
var app = angular.module('MyApp', []);
// Configure interceptor to add Authorization header
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('authInterceptor');
}]);
// Define the interceptor service
app.factory('authInterceptor', ['$q', function($q) {
return {
// Run before the request is sent
request: function(config) {
// Get the token from localStorage (or wherever you store it)
const token = localStorage.getItem('jwtToken');
if (token) {
// Add Authorization header
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
},
// Handle errors (optional)
responseError: function(rejection) {
if (rejection.status === 401) {
// Redirect to login if token is invalid/expired
window.location.href = '/login';
}
return $q.reject(rejection);
}
};
}]);Step 2: Test a Request#
With the interceptor in place, any $http request will automatically include the Authorization header:
// In a controller
app.controller('MyController', ['$http', function($http) {
$http.get('https://api.yourbackend.com/data')
.then(function(response) {
console.log('Success:', response.data); // Should work now!
});
}]);4.4 Test Preflight and Actual Requests#
To confirm the fix:
- Open Chrome DevTools (F12) → Network tab.
- Filter by
XHR/fetchrequests. - Trigger the AngularJS request (e.g., click a button).
You should see two requests:
- Preflight
OPTIONSrequest: Check the Response Headers to ensureAccess-Control-Allow-Headers: Authorizationis present. - Actual request (e.g.,
GET): Check the Request Headers to confirmAuthorization: Bearer YOUR_TOKENis sent.
5. Common Pitfalls to Avoid#
Even with the above steps, issues can arise. Watch for these mistakes:
Pitfall 1: Missing Authorization in Access-Control-Allow-Headers#
If the backend’s Access-Control-Allow-Headers doesn’t include Authorization, the browser will block the request. Double-check this header!
Pitfall 2: Using Access-Control-Allow-Origin: * with Credentials#
If your app uses cookies or HTTP authentication, * is invalid for Access-Control-Allow-Origin. Use the explicit frontend origin (e.g., http://localhost:8080).
Pitfall 3: Server Blocking OPTIONS Requests#
Some firewalls or server configs (e.g., strict mod_security rules in Apache) block OPTIONS requests. Ensure OPTIONS is allowed in your server’s HTTP method whitelist.
Pitfall 4: Typos in Header Names#
Headers are case-insensitive, but AngularJS and servers sometimes treat them as case-sensitive. Use lowercase (authorization) or match the server’s expected casing (e.g., Authorization).
Pitfall 5: Caching Stale Preflight Responses#
If you updated your server’s CORS config but the browser still uses old headers, clear the browser cache or set Access-Control-Max-Age to a low value (e.g., 0) temporarily.
6. Troubleshooting: Debugging Preflight Failures#
If the issue persists, use these tools to diagnose:
1. Browser DevTools Network Tab#
- Check the
OPTIONSrequest:- Status code: A
200 OKor204 No Contentmeans preflight succeeded.403/405means the server rejected it. - Response headers: Ensure
Access-Control-Allow-HeadersincludesAuthorization.
- Status code: A
2. Test with curl#
Bypass the browser to test the backend directly. Run this in your terminal:
# Test preflight OPTIONS request
curl -X OPTIONS https://api.yourbackend.com/data \
-H "Origin: http://localhost:8080" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: Authorization" \
-v # Verbose mode to see response headersLook for Access-Control-Allow-Headers: Authorization in the response.
3. Check Server Logs#
If the OPTIONS request isn’t reaching your backend, check server logs (e.g., Nginx’s access.log, Express.js console) to see if it’s being blocked by a firewall or routing rule.
7. Conclusion#
AngularJS authorization headers often fail due to unhandled CORS preflight requests. The fix requires two key steps:
- Backend: Configure the server to accept
OPTIONSrequests and return the correct CORS headers (especiallyAccess-Control-Allow-Headers: Authorization). - Frontend: Use AngularJS interceptors to globally inject the
Authorizationheader into requests.
By addressing preflight and CORS headers, you’ll ensure your tokens are sent successfully, and your AngularJS app communicates seamlessly with the backend.