Whenever you ask for a page on the web, your request has a lot of data attached to it. One part of your request (the “user-agent” string) describes your software environment—usually your operating system and your browser’s name and version number. Like many things on the web, this data is a convention, not a law. You are free to modify it however you want. This practice is known as user-agent spoofing.
If you’re ready to give it a try yourself (and you have Google Chrome), you can find the extension (and installation instructions) here. If you’re a developer interested in modifying headers from a Chrome extension, read on!
Implementation Details
Update II (1/24/12) It looks like Chrome 17 will ship with support for modifying the User-Agent string. (Click here for the Chromium issue and here for more info on the feature.) This renders the dedicated user-agent switching extension obsolete, but the approach outlined here may still be relevant in the context of a larger extension. Also remember the WebRequest API has many applications beyond this simple use-case!
Update I (1/3/12) In Chrome 17, the WebRequest API will lose its “experimental” designation and change slightly. This article, originally published in August 2011, has been updated to reflect these changes. For the more diff-minded developer, I’ve updated the example project with a single commit which documents the specific changes.
We’ll be using Chrome’s WebRequest API to accomplish this task. First off, you will have to request permission to use it in your extension’s manifest.json file:
”permissions”: [ “webRequest” ]
…although in our specific case, we’ll need some additional permissions: one for “blocking” all requests until we’ve completely processed them, and another for our extension to operate on all URLs. That means our manifest file will actually look more like this:
"permissions": [ "webRequest", “webRequestBlocking”, “<all_urls>” ]
I’ll be focusing on the “onBeforeSendHeaders” event for now. Behaviors like request blocking and redirecting can be achieved with other event handlers, but those will have to wait for another post.
You can bind your own listener to this event in your extension’s background page. Here is an example binding which outlines the necessary steps:
background.js
// The 'reqestFilter' parameter allows you to only listen for
// certain requests. Chrome 17 requires that, at the very least,
// it defines the URLs you wish to subscribe to. In the general
// case, we want to subscribe to all URL's, so we'll explicitly
// declare this requirement.
var requestFilter = {
urls: [ "<all_urls>" ]
},
// The 'extraInfoSpec' parameter modifies how Chrome calls your
// listener function. 'requestHeaders' ensures that the 'details'
// object has a key called 'requestHeaders' containing the headers,
// and 'blocking' ensures that the object your function returns is
// used to overwrite the headers
extraInfoSpec = ['requestHeaders','blocking'],
// Chrome will call your listener function in response to every
// HTTP request
handler = function( details ) {
var headers = details.requestHeaders,
blockingResponse = {};
// Each header parameter is stored in an array. Since Chrome
// makes no guarantee about the contents/order of this array,
// you'll have to iterate through it to find for the
// 'User-Agent' element
for( var i = 0, l = headers.length; i < l; ++i ) {
if( headers[i].name == 'User-Agent' ) {
headers[i].value = '>>> Your new user agent string here <<<';
break;
}
// If you want to modify other headers, this is the place to
// do it. Either remove the 'break;' statement and add in more
// conditionals or use a 'switch' statement on 'headers[i].name'
}
blockingResponse.requestHeaders = headers;
return blockingResponse;
};
chrome.webRequest.onBeforeSendHeaders.addListener( handler, requestFilter, extraInfoSpec );
…that should be enough to get you up and running.
Still Some Uncertainty
If you’re anything like me, after reading the documentation and the above example, you still have some questions. I’ll do my best to answer them:
What are the contents of requestHeaders
? The API documentation gives hints at what it doesn’t contain, but my experience has been varied. Here’s a printout of what I’ve seen, although I’ve received confirmation from Chrome developers that the contents are variable:
requestHeaders.json
[
{"name":"Pragma","value":"no-cache"},
{"name":"User-Agent","value":"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.15 Safari/535.1"},
{"name":"Accept","value":"text/css,*/*;q=0.1"},
{"name":"Cache-Control","value":"no-cache"},
{"name":"Referer","value":"http://mikepennisi.com/"},
{"name":"Accept-Encoding","value":"gzip,deflate,sdch"},
{"name":"Accept-Language","value":"en-US,en;q=0.8"},
{"name":"Accept-Charset","value":"ISO-8859-1,utf-8;q=0.7,*;q=0.3"},
{"name":"Cookie","value":"__utma=138710319.518446708.1311977381.1311977381.1311977381.1; __utmz=138710319.1311977381.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); ci_session=6YkLJxXZRFbZ5DLlpFCL8KG3cnggPjSvEtP7VCNRTv4JLphOrHGUqPX3pfOuURl0cfbMb9Gu4%2FcNnsxZyiTNxH%2B0hXZ4pElZush0NJNHHD7ReOV2hoHWRaDX1ikw4vWamoyB8WvLftxotbKX4ibITIwqBlQ0mqq%2BcIQRn5uW0ueFgwzt43b5Q6KBGYJ2RMXZrrT%2FgB0ViV8dofvXNmQ8qFueXOlotnnWx1KcTWyyiWDOIia2%2Bm5ZuknlbmvFMk5czOMf5y70S4oaK7iup7gAlaPSVD9%2FZE%2BzslzuCvvyiXM%3D"}
]
How do I detach a listener? Although currently undocumented, chrome.webRequest.onBeforeSendHeaders.removeListener()
seems to work. It may be safer (for now) to begin your listener function by checking an externally-scoped listenerIsActive
flag which you can set/unset from outside the function.
background-toggle.js
var listenerIsActive = true,
requestFilter = {
urls: [ "<all_urls>" ]
},
extraInfoSpec = ['requestHeaders','blocking'],
handler = function( details ) {
var headers = details.requestHeaders,
blockingResponse = {};
if( !listenerIsActive ) {
// If you take this approach, just make sure you return
// the headers, or else your "disabled" listener will
// delete all headers from future requests!
return {requestHeaders: details.requestHeaders};
}
// Your code here...
};
chrome.webRequest.onBeforeSendHeaders.addListener( handler, requestFilter, extraInfoSpec );
How can I use requestFilter to control which requests the listener responds to? The requestFilter parameter optionally lets you filter by tab ID and window ID. You can also specify a URL pattern to filter by–the pattern schema is documented here.
Hopefully, this has been a useful introduction to Chrome’s WebRequest API. As you have seen, it is now possible to spoof the user-agent string from an extension. The API seems to be stabilizing in preparation for its coming promotion from the “experimental” namespace. Be sure to reference the official documentation for the most up-to-date details.
Comments
We moved off of Disqus for data privacy and consent concerns, and are currently searching for a new commenting tool.
thanks!!!!!!!!!!!!!!!!!!!!!!!!
thank you… the official document drive me crazy. :). You example is really helpful.
nice post 🙂
a) webrequest is now stable.. at least here, only\u00a0chrome.webRequest.onBeforeSendHeaders.addListener works
b) at the second parameter, here, it needs\u00a0{urls: [\”<all_urls>\”]}
I only discovered this debugging at console window
so, a version that works here (for people coming from google):
chrome.webRequest.onBeforeSendHeaders.addListener(\u00a0 // Chrome will call your listener function in response to every\u00a0 // HTTP request\u00a0 function( details ) {\u00a0 \u00a0 var headers = details.requestHeaders,\u00a0 \u00a0 \u00a0 blockingResponse = {};\u00a0 \u00a0 // Each header parameter is stored in an array. Since Chrome\u00a0 \u00a0 // makes no guarantee about the contents/order of this array,\u00a0 \u00a0 // you’ll have to iterate through it to find for the\u00a0 \u00a0 // ‘User-Agent’ element\u00a0 \u00a0 for( var i = 0, l = headers.length; i < l; ++i ) {\u00a0 \u00a0 \u00a0 if( headers[i].name == ‘User-Agent’ ) {\u00a0 \u00a0 \u00a0 \u00a0 headers[i].value = ‘Woooohooo’;\u00a0 \u00a0 \u00a0 \u00a0 break;\u00a0 \u00a0 \u00a0 }\u00a0 \u00a0 \u00a0 // If you want to modify other headers, this is the place to\u00a0 \u00a0 \u00a0 // do it. Either remove the ‘break;’ statement and add in more\u00a0 \u00a0 \u00a0 // conditionals or use a ‘switch’ statement on ‘headers[i].name’\u00a0 \u00a0 }\u00a0 \u00a0 blockingResponse.requestHeaders = headers;\u00a0 \u00a0 return blockingResponse;\u00a0 },\u00a0 // The ‘reqestFilter’ parameter allows you to only listen for\u00a0 // certain requests–it’s not necessary for the general case,\u00a0 // so we’ll just pass an empty object\u00a0 {urls: [\”<all_urls>\”]},\u00a0 // The ‘extraInfoSpec’ parameter modifies how Chrome calls your\u00a0 // listener function. ‘requestHeaders’ ensures that the ‘details’\u00a0 // object has a key called ‘requestHeaders’ containing the headers,\u00a0 // and ‘blocking’ ensures that the object your function returns is\u00a0 // used to overwrite the headers\u00a0 [‘requestHeaders’,’blocking’]);
http://javascript.nopaste.d…
ugh
Thanks for the update, elias!\u00a0I’ve re-written the outdated aspects of the article.
Is it same for xhr requests???\u00a0
the last line of the first big code block on this page has the following code:
chrome.webRequest.onBeforeSendHeaders.addListener( handler, requestFiler, extraInfoSpec );
shouldnt the middle parameter be requestFilter, not requestFiler ?
You are correct, sir! I’ve corrected my mistake; thanks
🙂
btw, is there any way of setting the useragent string in the javascript navigator object as some sites rely on that for their browser detection….
If you modify your user-agent string with Chrome 17+ (as mentioned in the second update to this article), the browser will take care of the navigator object for you.
Alternatively, you could look into user scripts (i.e. Greasemonkey) to modify these values. This approach will work across browsers, but it will not affect your HTTP request headers.
i tried the code out on this page, it works for setting the headers, but it doesnt change the javascript navigator object entering\u00a0
navigator.userAgent on the console, returns chromes normal useragent string. can you please confirm any extra steps that are required for this to be also changed?
btw im pretty sure you cant set the useragent string in greasemonkey, as its a non writable object as far as i am aware…
Hi,
First of all hats off to you for posting such a nice article chrome extension development.
I am new to browser extension development. Last week I spent it all on learning Firefox plugin development and I was only able to make a popup menu. Chrome is much easier as compared to it. You just need a ‘manifest.json’ to make your very existence of the extension.
I got two questions here
1) how could I make a simple user agent with an ON/OFF button to use only one \”user agent string\” (let say Firefox v37.
Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0).
The download link for the user agent code on this page
(http://jugglinmike.github.i… does nothing or maybe I
am so dumb enough to figure it out how?
One thing I like in Firefox is they have an xml file that stores all the user agent strings.
2) Can I have that option here on chrome?
The download link still works for me. To install it, you will have to save the file to your desktop, visit chrome://extensions/, and then drag the file into that page . I can’t guarantee that it will still function, though, because it was built for Chrome 16 (over three years ago, believe it or not).
The latest version of Chrome currently supports this functionality out-of-the box. If you open up the developer tools (by pressing Ctrl + Shift + i), you will find a button in the top-right of the interface labeled \”Show drawer\”. Within the drawer, there is an \”Emulation tab\”. Within *that* tab, there is a dropdown for labeled \”Spoof user agent\” which optionally allows you to specify any arbitrary user agent string.
Hope this helps!
It works but the ‘options.html’ page does not popup when you click the ‘UA’ icon. So one has to manually select values from the ‘options.html’ page.
Could you please guide me through this. All I want to use one user agent for an on/off button but there is too much of the text.
This is my popup.html file. the link is given in my manifest.json file where when I click my extension icon a small html page popups and I have two buttons \”On\” \”OFF\”.
I want when I click \”On\” this user agent is set->\”‘Mozilla/5.0 (iPhone; U; CPU iPhone OS 2_0 like Mac OS X; en-us) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5A347 Safari/525.20’\”
………………………..popup.html……….
<html>
<head>
</head>
<body>
<input type=\”image\” id=\”UA_On\” src=\”green.png\” value=\”Activate User Agent\”/>
<body>
<div class=\”popupContainerDiv\”>
<input type=\”image\” id=\”UA_On\” src=\”green.png\” value=\”Activate UA\”>
<input type=\”image\” id=\”UA_Off\” src=\”red.png\” value=\”UA off\”>
</div>
</body>
</html>
……………………End of popup.html…………..
Do I have to add/create a ‘.js’ file as an script defined in popup.html
or a func inside \”<input type=\”image\” id=\”UA_Off\” src=\”red.png\” value=\”UA off\” func();=\”\”/>\”
and what would be the simplest line of code that I can use to change my user agent lets say
\”\”‘Mozilla/5.0 (iPhone; U; CPU iPhone OS 2_0 like Mac OS X; en-us)
AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5A347
Safari/525.20’\”\”
Please help me
Can you help with my question here ? Thanks
http://stackoverflow.com/qu…