├── samples ├── mixed │ ├── icon128.png │ ├── popup.html │ ├── manifest.json │ ├── options.html │ └── options.js ├── tasks │ ├── icon128.png │ ├── manifest.json │ ├── popup.html │ └── popup.js ├── fbmusic │ ├── icon128.png │ ├── manifest.json │ ├── popup.html │ └── popup.js └── cp-oauth2.sh ├── lib ├── oauth2.html ├── oauth2_finish.js ├── oauth2_inject.js ├── adapters │ ├── sample.js │ ├── facebook.js │ ├── bitly.js │ ├── feedly.js │ ├── google.js │ └── github.js └── oauth2.js ├── README.md └── COPYING /samples/mixed/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borismus/oauth2-extensions/HEAD/samples/mixed/icon128.png -------------------------------------------------------------------------------- /samples/tasks/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borismus/oauth2-extensions/HEAD/samples/tasks/icon128.png -------------------------------------------------------------------------------- /samples/fbmusic/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borismus/oauth2-extensions/HEAD/samples/fbmusic/icon128.png -------------------------------------------------------------------------------- /samples/cp-oauth2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | for dir in * 4 | do 5 | if [ "`basename $0`" != "$dir" ] 6 | then 7 | cp -r ../lib/ $dir/oauth2/ 8 | echo "Copied OAuth 2.0 library to $dir" 9 | fi 10 | done 11 | -------------------------------------------------------------------------------- /samples/fbmusic/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Facebook Music", 3 | "version": "1.0", 4 | "description": "Shows a list of your favorite music according to facebook. OAuth 2.0 Extension Demo", 5 | "manifest_version": 2, 6 | "icons": { 7 | "128": "icon128.png" 8 | }, 9 | "browser_action": { 10 | "default_title": "Facebook Music OAuth Example", 11 | "default_icon": "icon128.png", 12 | "default_popup": "popup.html" 13 | }, 14 | "content_scripts": [ 15 | { 16 | "matches": ["http://www.facebook.com/robots.txt*"], 17 | "js": ["oauth2/oauth2_inject.js"], 18 | "run_at": "document_start" 19 | } 20 | ], 21 | "permissions": [ 22 | "https://graph.facebook.com/" 23 | ], 24 | "web_accessible_resources" : [ 25 | "oauth2/oauth2.html" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /samples/tasks/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Google Tasks OAuth 2.0", 3 | "version": "1.0", 4 | "description": "Adds a Google task. OAuth 2.0 Extension Demo", 5 | "icons": { 6 | "128": "icon128.png" 7 | }, 8 | "manifest_version":2, 9 | "browser_action": { 10 | "default_title": "OAuth 2.0", 11 | "default_icon": "icon128.png", 12 | "default_popup": "popup.html" 13 | }, 14 | "content_scripts": [ 15 | { 16 | "matches": ["http://www.google.com/robots.txt*"], 17 | "js": ["oauth2/oauth2_inject.js"], 18 | "run_at": "document_start" 19 | } 20 | ], 21 | "permissions": [ 22 | "https://accounts.google.com/o/oauth2/token", 23 | "https://www.googleapis.com/" 24 | ], 25 | "web_accessible_resources" : [ 26 | "oauth2/oauth2.html" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /lib/oauth2.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | OAuth 2.0 Finish Page 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /samples/mixed/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | OAuth 2.0 Popup 21 | 22 | 23 | 24 | 28 | 29 | -------------------------------------------------------------------------------- /samples/fbmusic/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | OAuth 2.0 Popup 21 | 22 | 23 | 27 | 28 | 29 | 30 |

According to Facebook, you like the following music:

31 | 33 | 34 | -------------------------------------------------------------------------------- /samples/mixed/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Multi OAuth 2.0", 3 | "version": "1.0", 4 | "description": "OAuth 2.0 Extension Demo", 5 | "manifest_version": 2, 6 | "icons": { 7 | "128": "icon128.png" 8 | }, 9 | "browser_action": { 10 | "default_title": "OAuth 2.0", 11 | "default_icon": "icon128.png", 12 | "default_popup": "options.html" 13 | }, 14 | "options_page": "options.html", 15 | "content_scripts": [ 16 | { 17 | "matches": ["http://www.facebook.com/robots.txt*"], 18 | "js": ["oauth2/oauth2_inject.js"], 19 | "run_at": "document_start" 20 | }, 21 | { 22 | "matches": ["http://www.google.com/robots.txt*"], 23 | "js": ["oauth2/oauth2_inject.js"], 24 | "run_at": "document_start" 25 | }, 26 | { 27 | "matches": ["https://github.com/robots.txt*"], 28 | "js": ["oauth2/oauth2_inject.js"], 29 | "run_at": "document_start" 30 | } 31 | ], 32 | "permissions": [ 33 | "https://graph.facebook.com/", 34 | "https://accounts.google.com/o/oauth2/token", 35 | "https://github.com/" 36 | ], 37 | "web_accessible_resources" : [ 38 | "oauth2/oauth2.html" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /samples/tasks/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | OAuth 2.0 Popup 21 | 22 | 23 | 26 | 27 | 28 |
29 | 30 | 31 |
32 |
33 | Successfully created new task with ID . 34 |
35 | -------------------------------------------------------------------------------- /lib/oauth2_finish.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // This script serves as an intermediary between oauth2.html and oauth2.js. 18 | 19 | // Get all query string parameters from the original URL. 20 | var url = decodeURIComponent(window.location.href.match(/&from=([^&]+)/)[1]); 21 | var index = url.indexOf('?'); 22 | if (index > -1) { 23 | url = url.substring(0, index); 24 | } 25 | 26 | // Derive adapter name from URI and then finish the process. 27 | var adapterName = OAuth2.lookupAdapterName(url); 28 | var finisher = new OAuth2(adapterName, OAuth2.FINISH); -------------------------------------------------------------------------------- /samples/mixed/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | OAuth 2.0 Options 21 | 22 | 23 | 27 | 28 | 29 | 30 |

OAuth 2.0 Permissions

31 | 34 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/oauth2_inject.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. All Rights Reserved. 3 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | // This script servers as an intermediary between oauth2.js and 19 | // oauth2.html 20 | 21 | // Get all ? params from this URL 22 | var url = window.location.href; 23 | var params = '?'; 24 | var index = url.indexOf(params); 25 | if (index > -1) { 26 | params = url.substring(index); 27 | } 28 | 29 | // Also append the current URL to the params 30 | params += '&from=' + encodeURIComponent(url); 31 | 32 | // Redirect back to the extension itself so that we have priveledged 33 | // access again 34 | var redirect = chrome.extension.getURL('oauth2/oauth2.html'); 35 | window.location = redirect + params; 36 | -------------------------------------------------------------------------------- /lib/adapters/sample.js: -------------------------------------------------------------------------------- 1 | OAuth2.adapter('sample', { 2 | /** 3 | * @return {URL} URL to the page that returns the authorization code 4 | */ 5 | authorizationCodeURL: function(config) { 6 | return ''; 7 | }, 8 | 9 | /** 10 | * @return {URL} URL to the page that we use to inject the content 11 | * script into 12 | */ 13 | redirectURL: function(config) { 14 | return ''; 15 | }, 16 | 17 | /** 18 | * @return {String} Authorization code for fetching the access token 19 | */ 20 | parseAuthorizationCode: function(url) { 21 | return ''; 22 | }, 23 | 24 | /** 25 | * @return {URL} URL to the access token providing endpoint 26 | */ 27 | accessTokenURL: function() { 28 | return ''; 29 | }, 30 | 31 | /** 32 | * @return {String} HTTP method to use to get access tokens 33 | */ 34 | accessTokenMethod: function() { 35 | return 'POST'; 36 | }, 37 | 38 | /** 39 | * @return {Object} The payload to use when getting the access token 40 | */ 41 | accessTokenParams: function(authorizationCode, config) { 42 | return {}; 43 | }, 44 | 45 | /** 46 | * @return {Object} Object containing accessToken {String}, 47 | * refreshToken {String} and expiresIn {Int} 48 | */ 49 | parseAccessToken: function(response) { 50 | return { 51 | accessToken: '', 52 | refreshToken: '', 53 | expiresIn: Number.MAX_VALUE 54 | }; 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /lib/adapters/facebook.js: -------------------------------------------------------------------------------- 1 | OAuth2.adapter('facebook', { 2 | authorizationCodeURL: function(config) { 3 | return ('https://www.facebook.com/dialog/oauth?' + 4 | 'client_id={{CLIENT_ID}}&' + 5 | 'redirect_uri={{REDIRECT_URI}}&' + 6 | 'scope={{API_SCOPE}}') 7 | .replace('{{CLIENT_ID}}', config.clientId) 8 | .replace('{{REDIRECT_URI}}', this.redirectURL(config)) 9 | .replace('{{API_SCOPE}}', config.apiScope); 10 | }, 11 | 12 | redirectURL: function(config) { 13 | return 'http://www.facebook.com/robots.txt'; 14 | }, 15 | 16 | parseAuthorizationCode: function(url) { 17 | // TODO: error handling (URL may have 18 | // ?error=asfasfasiof&error_code=43 etc) 19 | return url.match(/[&\?]code=([^&]+)/)[1]; 20 | }, 21 | 22 | accessTokenURL: function() { 23 | return 'https://graph.facebook.com/oauth/access_token'; 24 | }, 25 | 26 | accessTokenMethod: function() { 27 | return 'GET'; 28 | }, 29 | 30 | accessTokenParams: function(authorizationCode, config) { 31 | return { 32 | code: authorizationCode, 33 | client_id: config.clientId, 34 | client_secret: config.clientSecret, 35 | redirect_uri: this.redirectURL(config) 36 | }; 37 | }, 38 | 39 | parseAccessToken: function(response) { 40 | return { 41 | accessToken: response.match(/access_token=([^&]*)/)[1], 42 | expiresIn: response.match(/expires=([^&]*)/)[1] 43 | }; 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /lib/adapters/bitly.js: -------------------------------------------------------------------------------- 1 | OAuth2.adapter('bitly', { 2 | 3 | authorizationCodeURL: function(config) { 4 | return ('https://bitly.com/oauth/authorize?' + 5 | 'client_id={{CLIENT_ID}}&' + 6 | 'redirect_uri={{REDIRECT_URI}}') 7 | .replace('{{CLIENT_ID}}', config.clientId) 8 | .replace('{{REDIRECT_URI}}', this.redirectURL(config)); 9 | }, 10 | 11 | redirectURL: function(config) { 12 | return 'http://bitly.com/robots.txt'; 13 | }, 14 | 15 | parseAuthorizationCode: function(url) { 16 | // TODO: Error handling (URL may have ?error=foo_bar&error_code=43 etc). 17 | return url.match(/[&\?]code=([^&]+)/)[1]; 18 | }, 19 | 20 | accessTokenURL: function() { 21 | return 'https://api-ssl.bitly.com/oauth/access_token'; 22 | }, 23 | 24 | accessTokenMethod: function() { 25 | return 'POST'; 26 | }, 27 | 28 | accessTokenParams: function(authorizationCode, config) { 29 | return { 30 | code: authorizationCode, 31 | client_id: config.clientId, 32 | client_secret: config.clientSecret, 33 | redirect_uri: this.redirectURL(config), 34 | grant_type: 'authorization_code' 35 | }; 36 | }, 37 | 38 | parseAccessToken: function(response) { 39 | return { 40 | accessToken: response.match(/access_token=([^&]*)/)[1], 41 | apiKey: response.match(/apiKey=([^&]*)/)[1], 42 | expiresIn: Number.MAX_VALUE, 43 | login: response.match(/login=([^&]*)/)[1] 44 | }; 45 | } 46 | 47 | }); -------------------------------------------------------------------------------- /lib/adapters/feedly.js: -------------------------------------------------------------------------------- 1 | OAuth2.adapter('feedly', { 2 | authorizationCodeURL: function(config) { 3 | return ('https://cloud.feedly.com/v3/auth/auth?' + 4 | 'response_type=code&' + 5 | 'client_id={{CLIENT_ID}}&' + 6 | 'redirect_uri={{REDIRECT_URI}}&' + 7 | 'scope={{API_SCOPE}}') 8 | .replace('{{CLIENT_ID}}', config.clientId) 9 | .replace('{{REDIRECT_URI}}', this.redirectURL(config)) 10 | .replace('{{API_SCOPE}}', config.apiScope); 11 | }, 12 | 13 | redirectURL: function(config) { 14 | return 'http://www.feedly.com/robots.txt'; 15 | }, 16 | 17 | parseAuthorizationCode: function(url) { 18 | return url.match(/[&\?]code=([^&]+)/)[1]; 19 | }, 20 | 21 | accessTokenURL: function() { 22 | return 'https://cloud.feedly.com/v3/auth/token'; 23 | }, 24 | 25 | accessTokenMethod: function() { 26 | return 'POST'; 27 | }, 28 | 29 | accessTokenParams: function(authorizationCode, config) { 30 | return { 31 | code: authorizationCode, 32 | client_id: config.clientId, 33 | client_secret: config.clientSecret, 34 | grant_type: "authorization_code", 35 | redirect_uri: this.redirectURL(config) 36 | }; 37 | }, 38 | 39 | parseAccessToken: function(response) { 40 | var parsedResponse = JSON.parse(response); 41 | return { 42 | userId: parsedResponse.id, 43 | accessToken: parsedResponse.access_token, 44 | refreshToken: parsedResponse.refresh_token, 45 | expiresIn: parsedResponse.expires_in 46 | }; 47 | } 48 | }); 49 | -------------------------------------------------------------------------------- /lib/adapters/google.js: -------------------------------------------------------------------------------- 1 | OAuth2.adapter('google', { 2 | authorizationCodeURL: function(config) { 3 | return ('https://accounts.google.com/o/oauth2/auth?' + 4 | 'approval_prompt=force&' + 5 | 'client_id={{CLIENT_ID}}&' + 6 | 'redirect_uri={{REDIRECT_URI}}&' + 7 | 'scope={{API_SCOPE}}&' + 8 | 'access_type=offline&' + 9 | 'response_type=code') 10 | .replace('{{CLIENT_ID}}', config.clientId) 11 | .replace('{{REDIRECT_URI}}', this.redirectURL(config)) 12 | .replace('{{API_SCOPE}}', config.apiScope); 13 | }, 14 | 15 | redirectURL: function(config) { 16 | return 'http://www.google.com/robots.txt'; 17 | }, 18 | 19 | parseAuthorizationCode: function(url) { 20 | var error = url.match(/[&\?]error=([^&]+)/); 21 | if (error) { 22 | throw 'Error getting authorization code: ' + error[1]; 23 | } 24 | return url.match(/[&\?]code=([\w\/\-]+)/)[1]; 25 | }, 26 | 27 | accessTokenURL: function() { 28 | return 'https://accounts.google.com/o/oauth2/token'; 29 | }, 30 | 31 | accessTokenMethod: function() { 32 | return 'POST'; 33 | }, 34 | 35 | accessTokenParams: function(authorizationCode, config) { 36 | return { 37 | code: authorizationCode, 38 | client_id: config.clientId, 39 | client_secret: config.clientSecret, 40 | redirect_uri: this.redirectURL(config), 41 | grant_type: 'authorization_code' 42 | }; 43 | }, 44 | 45 | parseAccessToken: function(response) { 46 | var parsedResponse = JSON.parse(response); 47 | return { 48 | accessToken: parsedResponse.access_token, 49 | refreshToken: parsedResponse.refresh_token, 50 | expiresIn: parsedResponse.expires_in 51 | }; 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /samples/fbmusic/popup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. All Rights Reserved. 3 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var facebook = new OAuth2('facebook', { 18 | client_id: '177955888930840', 19 | client_secret: 'b42a5741bd3d6de6ac591c7b0e279c9f', 20 | api_scope: 'read_stream,user_likes' 21 | }); 22 | facebook.authorize(function() { 23 | 24 | //document.addEventListener('DOMContentLoaded', function() { 25 | 26 | // Make an XHR that creates the task 27 | var xhr = new XMLHttpRequest(); 28 | xhr.onreadystatechange = function(event) { 29 | if (xhr.readyState == 4) { 30 | if(xhr.status == 200) { 31 | // Great success: parse response with JSON 32 | var parsed = JSON.parse(xhr.responseText); 33 | var html = ''; 34 | parsed.data.forEach(function(item, index) { 35 | html += '
  • ' + item.name + '
  • '; 36 | }); 37 | document.querySelector('#music').innerHTML = html; 38 | return; 39 | 40 | } else { 41 | // Request failure: something bad happened 42 | } 43 | } 44 | }; 45 | 46 | xhr.open('GET', 'https://graph.facebook.com/me/music', true); 47 | xhr.setRequestHeader('Content-Type', 'application/json'); 48 | xhr.setRequestHeader('Authorization', 'OAuth ' + facebook.getAccessToken()); 49 | 50 | xhr.send(); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /samples/mixed/options.js: -------------------------------------------------------------------------------- 1 | var google = new OAuth2('google', { 2 | client_id: '952993494713-h12m6utvq8g8d8et8n2i68plbrr6cr4d.apps.googleusercontent.com', 3 | client_secret: 'IZ4hBSbosuhoWAX4lyAomm-R', 4 | api_scope: 'https://www.googleapis.com/auth/tasks' 5 | }); 6 | 7 | var facebook = new OAuth2('facebook', { 8 | client_id: '177955888930840', 9 | client_secret: 'b42a5741bd3d6de6ac591c7b0e279c9f', 10 | api_scope: 'read_stream,user_likes' 11 | }); 12 | 13 | var github = new OAuth2('github', { 14 | client_id: '09450dfdc3ae76768b08', 15 | client_secret: '8ecfc23e0dba1ce1a295fbabc01fa71db4b80261', 16 | }); 17 | 18 | function authorize(providerName) { 19 | var provider = window[providerName]; 20 | provider.authorize(checkAuthorized); 21 | } 22 | 23 | function clearAuthorized() { 24 | console.log('clear'); 25 | ['google', 'facebook', 'github'].forEach(function(providerName) { 26 | var provider = window[providerName]; 27 | provider.clearAccessToken(); 28 | }); 29 | checkAuthorized(); 30 | } 31 | 32 | function checkAuthorized() { 33 | console.log('checkAuthorized'); 34 | ['google', 'facebook', 'github'].forEach(function(providerName) { 35 | var provider = window[providerName]; 36 | var button = document.querySelector('#' + providerName); 37 | if (provider.hasAccessToken()) { 38 | button.classList.add('authorized'); 39 | } else { 40 | button.classList.remove('authorized'); 41 | } 42 | }); 43 | } 44 | 45 | document.addEventListener('DOMContentLoaded', function () { 46 | document.querySelector('button#google').addEventListener('click', function() { authorize('google'); }); 47 | document.querySelector('button#github').addEventListener('click', function() { authorize('github'); }); 48 | document.querySelector('button#facebook').addEventListener('click', function() { authorize('facebook'); }); 49 | document.querySelector('button#clear').addEventListener('click', function() { clearAuthorized() }); 50 | 51 | checkAuthorized(); 52 | }); 53 | 54 | -------------------------------------------------------------------------------- /lib/adapters/github.js: -------------------------------------------------------------------------------- 1 | OAuth2.adapter('github', { 2 | /** 3 | * @return {URL} URL to the page that returns the authorization code 4 | */ 5 | authorizationCodeURL: function(config) { 6 | return ('https://github.com/login/oauth/authorize?' + 7 | 'client_id={{CLIENT_ID}}&' + 8 | 'scope={{API_SCOPE}}&' + 9 | 'redirect_uri={{REDIRECT_URI}}') 10 | .replace('{{CLIENT_ID}}', config.clientId) 11 | .replace('{{API_SCOPE}}', config.apiScope) 12 | .replace('{{REDIRECT_URI}}', this.redirectURL(config)); 13 | }, 14 | 15 | /** 16 | * @return {URL} URL to the page that we use to inject the content 17 | * script into 18 | */ 19 | redirectURL: function(config) { 20 | return 'https://github.com/robots.txt'; 21 | }, 22 | 23 | /** 24 | * @return {String} Authorization code for fetching the access token 25 | */ 26 | parseAuthorizationCode: function(url) { 27 | var error = url.match(/[&\?]error=([^&]+)/); 28 | if (error) { 29 | throw 'Error getting authorization code: ' + error[1]; 30 | } 31 | return url.match(/[&\?]code=([\w\/\-]+)/)[1]; 32 | }, 33 | 34 | /** 35 | * @return {URL} URL to the access token providing endpoint 36 | */ 37 | accessTokenURL: function() { 38 | return 'https://github.com/login/oauth/access_token'; 39 | }, 40 | 41 | /** 42 | * @return {String} HTTP method to use to get access tokens 43 | */ 44 | accessTokenMethod: function() { 45 | return 'POST'; 46 | }, 47 | 48 | /** 49 | * @return {Object} The payload to use when getting the access token 50 | */ 51 | accessTokenParams: function(authorizationCode, config) { 52 | return { 53 | code: authorizationCode, 54 | client_id: config.clientId, 55 | client_secret: config.clientSecret, 56 | redirect_uri: this.redirectURL(config), 57 | grant_type: 'authorization_code' 58 | }; 59 | }, 60 | 61 | /** 62 | * @return {Object} Object containing accessToken {String}, 63 | * refreshToken {String} and expiresIn {Int} 64 | */ 65 | parseAccessToken: function(response) { 66 | return { 67 | accessToken: response.match(/access_token=([^&]*)/)[1], 68 | expiresIn: Number.MAX_VALUE 69 | }; 70 | } 71 | }); 72 | -------------------------------------------------------------------------------- /samples/tasks/popup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. All Rights Reserved. 3 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var google = new OAuth2('google', { 18 | client_id: '952993494713-h12m6utvq8g8d8et8n2i68plbrr6cr4d.apps.googleusercontent.com', 19 | client_secret: 'IZ4hBSbosuhoWAX4lyAomm-R', 20 | api_scope: 'https://www.googleapis.com/auth/tasks' 21 | }); 22 | 23 | google.authorize(function() { 24 | 25 | var TASK_CREATE_URL = 'https://www.googleapis.com/tasks/v1/lists/@default/tasks'; 26 | 27 | var form = document.getElementById('form'); 28 | var success = document.getElementById('success'); 29 | 30 | // Hook up the form to create a new task with Google Tasks 31 | form.addEventListener('submit', function(event) { 32 | event.preventDefault(); 33 | var input = document.getElementById('input'); 34 | createTodo(input.value); 35 | }); 36 | 37 | function createTodo(task) { 38 | // Make an XHR that creates the task 39 | var xhr = new XMLHttpRequest(); 40 | xhr.onreadystatechange = function(event) { 41 | if (xhr.readyState == 4) { 42 | if(xhr.status == 200) { 43 | // Great success: parse response with JSON 44 | var task = JSON.parse(xhr.responseText); 45 | document.getElementById('taskid').innerHTML = task.id; 46 | form.style.display = 'none'; 47 | success.style.display = 'block'; 48 | 49 | } else { 50 | // Request failure: something bad happened 51 | } 52 | } 53 | }; 54 | 55 | var message = JSON.stringify({ 56 | title: task 57 | }); 58 | 59 | xhr.open('POST', TASK_CREATE_URL, true); 60 | 61 | xhr.setRequestHeader('Content-Type', 'application/json'); 62 | xhr.setRequestHeader('Authorization', 'OAuth ' + google.getAccessToken()); 63 | 64 | xhr.send(message); 65 | } 66 | 67 | }); 68 | 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecation note 2 | 3 | The [Chrome Identity API][identity] makes this drop-in library obsolete. 4 | 5 | [identity]: http://developer.chrome.com/apps/app_identity.html 6 | 7 | # Original docs 8 | 9 | This is the OAuth 2.0 library for Chrome Extensions. It's available on 10 | both [github][] and on [google code][]. 11 | 12 | [Sample extensions][samples] that use this library can be found in this same 13 | distribution, but please note that you will need to run the 14 | `cp-oauth2.sh` script inside the `samples` directory to get these 15 | samples to work. 16 | 17 | # Thanks, contributors! 18 | 19 | Many thanks to [neocotic@](https://github.com/neocotic) and other 20 | contributors for their great work in keeping this library up-to-date. 21 | 22 | # How to use this library 23 | 24 | Register your application with an OAuth 2.0 endpoint that you'd like to 25 | use. If it's a Google API you're calling, go to the [Google APIs][gapi] 26 | page, create your application and note your client ID and client secret. 27 | For more info on this, check out the [Google OAuth 2.0][goauth2] docs. 28 | When you setup your application, you will be asked to provide redirect 29 | URI(s). Please provide the URI that corresponds to the service you're 30 | using. 31 | 32 | Here's a table that will come in handy: 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
    AdapterRedirect URIAccess Token URI
    googlehttp://www.google.com/robots.txthttps://accounts.google.com/o/oauth2/token
    facebookhttp://www.facebook.com/robots.txthttps://graph.facebook.com/oauth/access_token
    githubhttps://github.com/robots.txthttps://github.com/login/oauth/access_token
    bitlyhttp://bitly.com/robots.txthttps://api-ssl.bitly.com/oauth/access_token
    feedlyhttp://www.feedly.com/robots.txthttps://cloud.feedly.com/v3/auth/token
    66 | 67 | #### Step 1: Copy library 68 | 69 | You will need to copy the [oauth2 library][oauth2crx] into your chrome 70 | extension root into a directory called `oauth2`. 71 | 72 | #### Step 2: Inject content script 73 | 74 | Then you need to modify your manifest.json file to include a content 75 | script at the redirect URL used by the Google adapter. The "matches" 76 | redirect URI can be looked up in the table above: 77 | 78 | "content_scripts": [ 79 | { 80 | "matches": ["http://www.google.com/robots.txt*"], 81 | "js": ["oauth2/oauth2_inject.js"], 82 | "run_at": "document_start" 83 | } 84 | ], 85 | 86 | #### Step 3: Allow access token URL 87 | 88 | Also, you will need to add a permission to Google's access token 89 | granting URL, since the library will do an XHR against it. The access 90 | token URI can be looked up in the table above as well. 91 | 92 | "permissions": [ 93 | "https://accounts.google.com/o/oauth2/token" 94 | ], 95 | "web_accessible_resources": [ 96 | "oauth2/oauth2.html" 97 | ], 98 | 99 | #### Step 4: Include the OAuth 2.0 library 100 | 101 | Next, in your extension's code, you should include the OAuth 2.0 102 | library: 103 | 104 | 105 | 106 | #### Step 5: Configure the OAuth 2.0 endpoint 107 | 108 | And configure your OAuth 2 connection by providing clientId, 109 | clientSecret and apiScopes from the registration page. The authorize() 110 | method may create a new popup window for the user to grant your 111 | extension access to the OAuth2 endpoint. 112 | 113 | var googleAuth = new OAuth2('google', { 114 | client_id: '17755888930840', 115 | client_secret: 'b4a5741bd3d6de6ac591c7b0e279c9f', 116 | api_scope: 'https://www.googleapis.com/auth/tasks' 117 | }); 118 | 119 | googleAuth.authorize(function() { 120 | // Ready for action, can now make requests with 121 | googleAuth.getAccessToken() 122 | }); 123 | 124 | #### Step 6: Use the access token 125 | 126 | Now that your user has an access token via `auth.getAccessToken()`, you 127 | can request protected data by adding the accessToken as a request header 128 | 129 | xhr.setRequestHeader('Authorization', 'OAuth ' + myAuth.getAccessToken()) 130 | 131 | or by passing it as part of the URL (depending on your particular impl): 132 | 133 | myUrl + '?oauth_token=' + myAuth.getAccessToken(); 134 | 135 | **Note**: if you have multiple OAuth 2.0 endpoints that you would like 136 | to authorize with, you can do that too! Just inject content scripts and 137 | add permissions for all of the providers you would like to authorize 138 | with. 139 | 140 | For more information about this library, please see this [blog 141 | post][blog]. 142 | 143 | 144 | [gapi]: https://code.google.com/apis/console/ 145 | [goauth2]: http://code.google.com/apis/accounts/docs/OAuth2.html 146 | [oauth crx]: http://code.google.com/chrome/extensions/tut_oauth.html 147 | [oauth2crx]: https://github.com/borismus/oauth2-extensions/tree/master/lib 148 | 149 | [github]: https://github.com/borismus/oauth2-extensions/ 150 | [google code]: http://code.google.com/p/oauth2-extensions/ 151 | [samples]: https://github.com/borismus/oauth2-extensions/samples 152 | [blog]: http://smus.com/oauth2-chrome-extensions 153 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2011 Google Inc 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /lib/oauth2.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. All Rights Reserved. 3 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | /** 19 | * Constructor 20 | * 21 | * @param {String} adapterName name of the adapter to use for this OAuth 2 22 | * @param {Object} config Containing clientId, clientSecret and apiScope 23 | * @param {String} config Alternatively, OAuth2.FINISH for the finish flow 24 | */ 25 | var OAuth2 = function(adapterName, config) { 26 | this.adapterName = adapterName; 27 | var that = this; 28 | OAuth2.loadAdapter(adapterName, function() { 29 | that.adapter = OAuth2.adapters[adapterName]; 30 | if (config == OAuth2.FINISH) { 31 | that.finishAuth(); 32 | } else if (config) { 33 | that.updateLocalStorage(); 34 | 35 | var data = that.get(); 36 | data.clientId = config.client_id; 37 | data.clientSecret = config.client_secret; 38 | data.apiScope = config.api_scope; 39 | that.setSource(data); 40 | } 41 | }); 42 | }; 43 | 44 | /** 45 | * Pass instead of config to specify the finishing OAuth flow. 46 | */ 47 | OAuth2.FINISH = 'finish'; 48 | 49 | /** 50 | * OAuth 2.0 endpoint adapters known to the library 51 | */ 52 | OAuth2.adapters = {}; 53 | OAuth2.adapterReverse = localStorage.oauth2_adapterReverse && 54 | JSON.parse(localStorage.oauth2_adapterReverse) || {}; 55 | // Update the persisted adapterReverse in localStorage. 56 | if (localStorage.adapterReverse) { 57 | OAuth2.adapterReverse = JSON.parse(localStorage.adapterReverse); 58 | delete localStorage.adapterReverse; 59 | } 60 | 61 | /** 62 | * Consolidates the data stored in localStorage on the current adapter in to 63 | * a single JSON object. 64 | * The update should only ever happen once per adapter and will delete the old 65 | * obsolete entries in localStorage after copying their values. 66 | */ 67 | OAuth2.prototype.updateLocalStorage = function() { 68 | // Check if update is even required. 69 | if (this.getSource()) { 70 | return; 71 | } 72 | var data = {}; 73 | var variables = [ 74 | 'accessToken', 'accessTokenDate', 'apiScope', 'clientId', 'clientSecret', 75 | 'expiresIn', 'refreshToken' 76 | ]; 77 | // Check if a variable has already been persisted and then copy them. 78 | var key; 79 | for (var i = 0; i < variables.length; i++) { 80 | key = this.adapterName + '_' + variables[i]; 81 | if (localStorage.hasOwnProperty(key)) { 82 | data[variables[i]] = localStorage[key]; 83 | delete localStorage[key]; 84 | } 85 | } 86 | // Persist the new JSON object in localStorage. 87 | this.setSource(data); 88 | }; 89 | 90 | /** 91 | * Opens up an authorization popup window. This starts the OAuth 2.0 flow. 92 | * 93 | * @param {Function} callback Method to call when the user finished auth. 94 | */ 95 | OAuth2.prototype.openAuthorizationCodePopup = function(callback) { 96 | // Store a reference to the callback so that the newly opened window can call 97 | // it later. 98 | window['oauth-callback'] = callback; 99 | 100 | // Create a new tab with the OAuth 2.0 prompt 101 | chrome.tabs.create({url: this.adapter.authorizationCodeURL(this.getConfig())}, 102 | function(tab) { 103 | // 1. user grants permission for the application to access the OAuth 2.0 104 | // endpoint 105 | // 2. the endpoint redirects to the redirect URL. 106 | // 3. the extension injects a script into that redirect URL 107 | // 4. the injected script redirects back to oauth2.html, also passing 108 | // the redirect URL 109 | // 5. oauth2.html uses redirect URL to know what OAuth 2.0 flow to finish 110 | // (if there are multiple OAuth 2.0 adapters) 111 | // 6. Finally, the flow is finished and client code can call 112 | // myAuth.getAccessToken() to get a valid access token. 113 | }); 114 | }; 115 | 116 | /** 117 | * Gets access and refresh (if provided by endpoint) tokens 118 | * 119 | * @param {String} authorizationCode Retrieved from the first step in the process 120 | * @param {Function} callback Called back with 3 params: 121 | * access token, refresh token and expiry time 122 | */ 123 | OAuth2.prototype.getAccessAndRefreshTokens = function(authorizationCode, callback) { 124 | var that = this; 125 | // Make an XHR to get the token 126 | var xhr = new XMLHttpRequest(); 127 | xhr.addEventListener('readystatechange', function(event) { 128 | if (xhr.readyState == 4) { 129 | if (xhr.status == 200) { 130 | // Callback with the data (incl. tokens). 131 | callback(that.adapter.parseAccessToken(xhr.responseText)); 132 | } 133 | } 134 | }); 135 | 136 | var method = that.adapter.accessTokenMethod(); 137 | var items = that.adapter.accessTokenParams(authorizationCode, that.getConfig()); 138 | var key = null; 139 | if (method == 'POST') { 140 | var formData = new FormData(); 141 | for (key in items) { 142 | formData.append(key, items[key]); 143 | } 144 | xhr.open(method, that.adapter.accessTokenURL(), true); 145 | xhr.send(formData); 146 | } else if (method == 'GET') { 147 | var url = that.adapter.accessTokenURL(); 148 | var params = '?'; 149 | for (key in items) { 150 | params += encodeURIComponent(key) + '=' + 151 | encodeURIComponent(items[key]) + '&'; 152 | } 153 | xhr.open(method, url + params, true); 154 | xhr.send(); 155 | } else { 156 | throw method + ' is an unknown method'; 157 | } 158 | }; 159 | 160 | /** 161 | * Refreshes the access token using the currently stored refresh token 162 | * Note: this only happens for the Google adapter since all other OAuth 2.0 163 | * endpoints don't implement refresh tokens. 164 | * 165 | * @param {String} refreshToken A valid refresh token 166 | * @param {Function} callback On success, called with access token and expiry time and refresh token 167 | */ 168 | OAuth2.prototype.refreshAccessToken = function(refreshToken, callback) { 169 | var xhr = new XMLHttpRequest(); 170 | xhr.onreadystatechange = function(event) { 171 | if (xhr.readyState == 4) { 172 | if(xhr.status == 200) { 173 | console.log(xhr.responseText); 174 | // Parse response with JSON 175 | var obj = JSON.parse(xhr.responseText); 176 | // Callback with the tokens 177 | callback(obj.access_token, obj.expires_in, obj.refresh_token); 178 | } 179 | } 180 | }; 181 | 182 | var data = this.get(); 183 | var formData = new FormData(); 184 | formData.append('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8') 185 | formData.append('client_id', data.clientId); 186 | formData.append('client_secret', data.clientSecret); 187 | formData.append('refresh_token', refreshToken); 188 | formData.append('grant_type', 'refresh_token'); 189 | xhr.open('POST', this.adapter.accessTokenURL(), true); 190 | xhr.send(formData); 191 | }; 192 | 193 | /** 194 | * Extracts authorizationCode from the URL and makes a request to the last 195 | * leg of the OAuth 2.0 process. 196 | */ 197 | OAuth2.prototype.finishAuth = function() { 198 | var authorizationCode = null; 199 | var that = this; 200 | 201 | // Loop through existing extension views and excute any stored callbacks. 202 | function callback(error) { 203 | var views = chrome.extension.getViews(); 204 | for (var i = 0, view; view = views[i]; i++) { 205 | if (view['oauth-callback']) { 206 | view['oauth-callback'](error); 207 | // TODO: Decide whether it's worth it to scope the callback or not. 208 | // Currently, every provider will share the same callback address but 209 | // that's not such a big deal assuming that they check to see whether 210 | // the token exists instead of blindly trusting that it does. 211 | } 212 | } 213 | 214 | // Once we get here, close the current tab and we're good to go. 215 | // The following works around bug: crbug.com/84201 216 | window.open('', '_self', ''); 217 | window.close(); 218 | } 219 | 220 | try { 221 | authorizationCode = that.adapter.parseAuthorizationCode(window.location.href); 222 | console.log(authorizationCode); 223 | } catch (e) { 224 | console.error(e); 225 | callback(e); 226 | } 227 | 228 | that.getAccessAndRefreshTokens(authorizationCode, function(response) { 229 | var data = that.get(); 230 | data.accessTokenDate = new Date().valueOf(); 231 | 232 | // Set all data returned by the OAuth 2.0 provider. 233 | for (var name in response) { 234 | if (response.hasOwnProperty(name) && response[name]) { 235 | data[name] = response[name]; 236 | } 237 | } 238 | 239 | that.setSource(data); 240 | callback(); 241 | }); 242 | }; 243 | 244 | /** 245 | * @return True iff the current access token has expired 246 | */ 247 | OAuth2.prototype.isAccessTokenExpired = function() { 248 | var data = this.get(); 249 | return (new Date().valueOf() - data.accessTokenDate) > data.expiresIn * 1000; 250 | }; 251 | 252 | /** 253 | * Get the persisted adapter data in localStorage. Optionally, provide a 254 | * property name to only retrieve its value. 255 | * 256 | * @param {String} [name] The name of the property to be retrieved. 257 | * @return The data object or property value if name was specified. 258 | */ 259 | OAuth2.prototype.get = function(name) { 260 | var src = this.getSource(); 261 | var obj = src ? JSON.parse(src) : {}; 262 | return name ? obj[name] : obj; 263 | }; 264 | 265 | /** 266 | * Set the value of a named property on the persisted adapter data in 267 | * localStorage. 268 | * 269 | * @param {String} name The name of the property to change. 270 | * @param value The value to be set. 271 | */ 272 | OAuth2.prototype.set = function(name, value) { 273 | var obj = this.get(); 274 | obj[name] = value; 275 | this.setSource(obj); 276 | }; 277 | 278 | /** 279 | * Clear all persisted adapter data in localStorage. Optionally, provide a 280 | * property name to only clear its value. 281 | * 282 | * @param {String} [name] The name of the property to clear. 283 | */ 284 | OAuth2.prototype.clear = function(name) { 285 | if (name) { 286 | var obj = this.get(); 287 | delete obj[name]; 288 | this.setSource(obj); 289 | } else { 290 | delete localStorage['oauth2_' + this.adapterName]; 291 | } 292 | }; 293 | 294 | /** 295 | * Get the JSON string for the object stored in localStorage. 296 | * 297 | * @return {String} The source JSON string. 298 | */ 299 | OAuth2.prototype.getSource = function() { 300 | return localStorage['oauth2_' + this.adapterName]; 301 | }; 302 | 303 | /** 304 | * Set the JSON string for the object stored in localStorage. 305 | * 306 | * @param {Object|String} source The new JSON string/object to be set. 307 | */ 308 | OAuth2.prototype.setSource = function(source) { 309 | if (!source) { 310 | return; 311 | } 312 | if (typeof source !== 'string') { 313 | source = JSON.stringify(source); 314 | } 315 | localStorage['oauth2_' + this.adapterName] = source; 316 | }; 317 | 318 | /** 319 | * Get the configuration parameters to be passed to the adapter. 320 | * 321 | * @returns {Object} Contains clientId, clientSecret and apiScope. 322 | */ 323 | OAuth2.prototype.getConfig = function() { 324 | var data = this.get(); 325 | return { 326 | clientId: data.clientId, 327 | clientSecret: data.clientSecret, 328 | apiScope: data.apiScope 329 | }; 330 | }; 331 | 332 | /*********************************** 333 | * 334 | * STATIC ADAPTER RELATED METHODS 335 | * 336 | ***********************************/ 337 | 338 | /** 339 | * Loads an OAuth 2.0 adapter and calls back when it's loaded 340 | * 341 | * @param adapterName {String} The name of the JS file 342 | * @param callback {Function} Called as soon as the adapter has been loaded 343 | */ 344 | OAuth2.loadAdapter = function(adapterName, callback) { 345 | // If it's already loaded, don't load it again 346 | if (OAuth2.adapters[adapterName]) { 347 | callback(); 348 | return; 349 | } 350 | var head = document.querySelector('head'); 351 | var script = document.createElement('script'); 352 | script.type = 'text/javascript'; 353 | script.src = '/oauth2/adapters/' + adapterName + '.js'; 354 | script.addEventListener('load', function() { 355 | callback(); 356 | }); 357 | head.appendChild(script); 358 | }; 359 | 360 | /** 361 | * Registers an adapter with the library. This call is used by each adapter 362 | * 363 | * @param {String} name The adapter name 364 | * @param {Object} impl The adapter implementation 365 | * 366 | * @throws {String} If the specified adapter is invalid 367 | */ 368 | OAuth2.adapter = function(name, impl) { 369 | var implementing = 'authorizationCodeURL redirectURL accessTokenURL ' + 370 | 'accessTokenMethod accessTokenParams accessToken'; 371 | 372 | // Check for missing methods 373 | implementing.split(' ').forEach(function(method, index) { 374 | if (!method in impl) { 375 | throw 'Invalid adapter! Missing method: ' + method; 376 | } 377 | }); 378 | 379 | // Save the adapter in the adapter registry 380 | OAuth2.adapters[name] = impl; 381 | // Make an entry in the adapter lookup table 382 | OAuth2.adapterReverse[impl.redirectURL()] = name; 383 | // Store the the adapter lookup table in localStorage 384 | localStorage.oauth2_adapterReverse = JSON.stringify(OAuth2.adapterReverse); 385 | }; 386 | 387 | /** 388 | * Looks up the adapter name based on the redirect URL. Used by oauth2.html 389 | * in the second part of the OAuth 2.0 flow. 390 | * 391 | * @param {String} url The url that called oauth2.html 392 | * @return The adapter for the current page 393 | */ 394 | OAuth2.lookupAdapterName = function(url) { 395 | var adapterReverse = JSON.parse(localStorage.oauth2_adapterReverse); 396 | return adapterReverse[url]; 397 | }; 398 | 399 | /*********************************** 400 | * 401 | * PUBLIC API 402 | * 403 | ***********************************/ 404 | 405 | /** 406 | * Authorizes the OAuth authenticator instance. 407 | * 408 | * @param {Function} callback Tries to callback when auth is successful 409 | * Note: does not callback if grant popup required 410 | */ 411 | OAuth2.prototype.authorize = function(callback) { 412 | var that = this; 413 | OAuth2.loadAdapter(that.adapterName, function() { 414 | that.adapter = OAuth2.adapters[that.adapterName]; 415 | var data = that.get(); 416 | if (!data.accessToken) { 417 | // There's no access token yet. Start the authorizationCode flow 418 | that.openAuthorizationCodePopup(callback); 419 | } else if (that.isAccessTokenExpired()) { 420 | // There's an existing access token but it's expired 421 | if (data.refreshToken) { 422 | that.refreshAccessToken(data.refreshToken, function(at, exp, re) { 423 | var newData = that.get(); 424 | newData.accessTokenDate = new Date().valueOf(); 425 | newData.accessToken = at; 426 | newData.expiresIn = exp; 427 | newData.refreshToken = re; 428 | that.setSource(newData); 429 | // Callback when we finish refreshing 430 | if (callback) { 431 | callback(); 432 | } 433 | }); 434 | } else { 435 | // No refresh token... just do the popup thing again 436 | that.openAuthorizationCodePopup(callback); 437 | } 438 | } else { 439 | // We have an access token, and it's not expired yet 440 | if (callback) { 441 | callback(); 442 | } 443 | } 444 | }); 445 | }; 446 | 447 | /** 448 | * @returns A valid access token. 449 | */ 450 | OAuth2.prototype.getAccessToken = function() { 451 | return this.get('accessToken'); 452 | }; 453 | 454 | /** 455 | * Indicate whether or not a valid access token exists. 456 | * 457 | * @returns {Boolean} True if an access token exists; otherwise false. 458 | */ 459 | OAuth2.prototype.hasAccessToken = function() { 460 | return !!this.get('accessToken'); 461 | }; 462 | 463 | /** 464 | * Clears an access token, effectively "logging out" of the service. 465 | */ 466 | OAuth2.prototype.clearAccessToken = function() { 467 | this.clear('accessToken'); 468 | }; 469 | --------------------------------------------------------------------------------