mirror of
https://github.com/GoogleChrome/chrome-extensions-samples.git
synced 2026-03-27 13:29:34 +07:00
Add sample Identity API extension (#1432)
* Initial copy of _archive/apps/samples/identity * Modify to run be an extension rather than a Chrome App. * Update extension to manifest v3 * Update README * Remove references to chrome://identity-internals * Use Promises rather than callbacks See https://developer.chrome.com/docs/extensions/develop/migrate/api-calls#replace-callbacks * Apply code-review markups. * Apply more code-review markups * Format with prettier * Use Promise.catch instead of runtime.lastError * Update screenshot --------- Co-authored-by: Oliver Dunk <oliverdunk@google.com>
This commit is contained in:
21
api-samples/identity/README.md
Normal file
21
api-samples/identity/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# chrome.identity
|
||||
|
||||
A sample extension that uses the
|
||||
[Identity API](https://developer.chrome.com/docs/extensions/reference/api/identity)
|
||||
to request information of the logged in user and present this info on the
|
||||
screen. If the user has a profile picture, their profile image is also fetched
|
||||
and shown in the app.
|
||||
|
||||
## Overview
|
||||
|
||||
This extension uses the getAuthToken flow of the Identity API, so it only
|
||||
works with Google accounts. If you want to identify the user in a non-Google
|
||||
OAuth2 flow, you should use the launchWebAuthFlow method instead.
|
||||
|
||||

|
||||
|
||||
## Running this extension
|
||||
|
||||
1. Clone this repository.
|
||||
2. Load this directory in Chrome as an [unpacked extension](https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked).
|
||||
3. Click the extension icon to open the UI.
|
||||
BIN
api-samples/identity/assets/screenshot.png
Normal file
BIN
api-samples/identity/assets/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
197
api-samples/identity/identity.js
Executable file
197
api-samples/identity/identity.js
Executable file
@@ -0,0 +1,197 @@
|
||||
'use strict';
|
||||
|
||||
function onLoad() {
|
||||
const STATE_START = 1;
|
||||
const STATE_ACQUIRING_AUTHTOKEN = 2;
|
||||
const STATE_AUTHTOKEN_ACQUIRED = 3;
|
||||
|
||||
let state = STATE_START;
|
||||
|
||||
const signin_button = document.querySelector('#signin');
|
||||
signin_button.addEventListener('click', interactiveSignIn);
|
||||
|
||||
const userinfo_button = document.querySelector('#userinfo');
|
||||
userinfo_button.addEventListener(
|
||||
'click',
|
||||
getUserInfo.bind(userinfo_button, true)
|
||||
);
|
||||
|
||||
const revoke_button = document.querySelector('#revoke');
|
||||
revoke_button.addEventListener('click', revokeToken);
|
||||
|
||||
const user_info_div = document.querySelector('#user_info');
|
||||
|
||||
// Trying to get user's info without signing in, it will work if the
|
||||
// application was previously authorized by the user.
|
||||
getUserInfo(false);
|
||||
|
||||
function disableButton(button) {
|
||||
button.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
|
||||
function enableButton(button) {
|
||||
button.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
function changeState(newState) {
|
||||
state = newState;
|
||||
switch (state) {
|
||||
case STATE_START:
|
||||
enableButton(signin_button);
|
||||
disableButton(userinfo_button);
|
||||
disableButton(revoke_button);
|
||||
break;
|
||||
case STATE_ACQUIRING_AUTHTOKEN:
|
||||
displayOutput('Acquiring token...');
|
||||
disableButton(signin_button);
|
||||
disableButton(userinfo_button);
|
||||
disableButton(revoke_button);
|
||||
break;
|
||||
case STATE_AUTHTOKEN_ACQUIRED:
|
||||
disableButton(signin_button);
|
||||
enableButton(userinfo_button);
|
||||
enableButton(revoke_button);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function displayOutput(message) {
|
||||
let messageStr = message;
|
||||
if (typeof message != 'string') {
|
||||
messageStr = JSON.stringify(message);
|
||||
}
|
||||
|
||||
document.getElementById('__logarea').value = messageStr;
|
||||
}
|
||||
|
||||
function fetchWithAuth(method, url, interactive, callback) {
|
||||
let access_token;
|
||||
let retry = true;
|
||||
|
||||
getToken();
|
||||
|
||||
function getToken() {
|
||||
chrome.identity
|
||||
.getAuthToken({ interactive: interactive })
|
||||
.then((token) => {
|
||||
access_token = token.token;
|
||||
requestStart();
|
||||
})
|
||||
.catch((error) => {
|
||||
callback(error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function requestStart() {
|
||||
fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + access_token
|
||||
}
|
||||
}).then((response) => {
|
||||
if (response.status == 401 && retry) {
|
||||
retry = false;
|
||||
chrome.identity
|
||||
.removeCachedAuthToken({ token: access_token })
|
||||
.then(getToken);
|
||||
} else {
|
||||
callback(null, response.status, response);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getUserInfo(interactive) {
|
||||
// See https://developers.google.com/identity/openid-connect/openid-connect#obtaininguserprofileinformation
|
||||
fetchWithAuth(
|
||||
'GET',
|
||||
'https://openidconnect.googleapis.com/v1/userinfo',
|
||||
interactive,
|
||||
onUserInfoFetched
|
||||
);
|
||||
}
|
||||
|
||||
// Code updating the user interface, when the user information has been
|
||||
// fetched or displaying the error.
|
||||
function onUserInfoFetched(error, status, response) {
|
||||
if (!error && status == 200) {
|
||||
changeState(STATE_AUTHTOKEN_ACQUIRED);
|
||||
response.json().then((user_info) => {
|
||||
displayOutput(user_info);
|
||||
populateUserInfo(user_info);
|
||||
});
|
||||
} else {
|
||||
changeState(STATE_START);
|
||||
}
|
||||
}
|
||||
|
||||
function populateUserInfo(user_info) {
|
||||
if (!user_info || !user_info.picture) return;
|
||||
|
||||
user_info_div.innerText = 'Hello ' + user_info.name;
|
||||
|
||||
const imgElem = document.createElement('img');
|
||||
imgElem.src = user_info.picture;
|
||||
imgElem.style.width = '24px';
|
||||
user_info_div.insertAdjacentElement('afterbegin', imgElem);
|
||||
}
|
||||
|
||||
// OnClick event handlers for the buttons.
|
||||
|
||||
/**
|
||||
Retrieves a valid token. Since this is initiated by the user
|
||||
clicking in the Sign In button, we want it to be interactive -
|
||||
ie, when no token is found, the auth window is presented to the user.
|
||||
|
||||
Observe that the token does not need to be cached by the app.
|
||||
Chrome caches tokens and takes care of renewing when it is expired.
|
||||
In that sense, getAuthToken only goes to the server if there is
|
||||
no cached token or if it is expired. If you want to force a new
|
||||
token (for example when user changes the password on the service)
|
||||
you need to call removeCachedAuthToken()
|
||||
**/
|
||||
function interactiveSignIn() {
|
||||
changeState(STATE_ACQUIRING_AUTHTOKEN);
|
||||
console.log('interactiveSignIn');
|
||||
|
||||
// This is the normal flow for authentication/authorization on Google
|
||||
// properties. You need to add the oauth2 client_id and scopes to the app
|
||||
// manifest. The interactive param indicates if a new window will be opened
|
||||
// when the user is not yet authenticated or not.
|
||||
//
|
||||
// See https://developer.chrome.com/docs/extensions/reference/api/identity#method-getAuthToken
|
||||
chrome.identity
|
||||
.getAuthToken({ interactive: true })
|
||||
.then((token) => {
|
||||
displayOutput('Token acquired:\n' + token.token);
|
||||
changeState(STATE_AUTHTOKEN_ACQUIRED);
|
||||
})
|
||||
.catch((error) => {
|
||||
displayOutput(error.message);
|
||||
changeState(STATE_START);
|
||||
});
|
||||
}
|
||||
|
||||
function revokeToken() {
|
||||
user_info_div.innerHTML = '';
|
||||
chrome.identity
|
||||
.getAuthToken({ interactive: false })
|
||||
.then((current_token) => {
|
||||
// Remove the local cached token
|
||||
chrome.identity.removeCachedAuthToken({ token: current_token.token });
|
||||
|
||||
// Make a request to revoke token in the server.
|
||||
// See https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow#tokenrevoke
|
||||
fetch(
|
||||
'https://oauth2.googleapis.com/revoke?token=' + current_token.token,
|
||||
{ method: 'POST' }
|
||||
).then(() => {
|
||||
// Update the user interface accordingly
|
||||
changeState(STATE_START);
|
||||
displayOutput('Token revoked and removed from cache.');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', onLoad);
|
||||
30
api-samples/identity/index.html
Executable file
30
api-samples/identity/index.html
Executable file
@@ -0,0 +1,30 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Identity API Sample Extension</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<script src="identity.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>Identity API</h1>
|
||||
<div class="links">
|
||||
Learn more:
|
||||
<a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/identity">API docs</a>
|
||||
<a id="_open_snippets" href="https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/identity">Source</a>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
<div class="flows">
|
||||
<h2>OAuth on Google properties</h2>
|
||||
<div class="flow">
|
||||
<button id="signin">Sign in</button>
|
||||
<button id="userinfo" disabled>Get personal data</button>
|
||||
<button id="revoke" disabled>Revoke token</button>
|
||||
<div id="user_info"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log">
|
||||
<textarea id="__logarea" disabled></textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
6
api-samples/identity/main.js
Executable file
6
api-samples/identity/main.js
Executable file
@@ -0,0 +1,6 @@
|
||||
chrome.action.onClicked.addListener(() => {
|
||||
chrome.tabs.create({
|
||||
active: true,
|
||||
url: "index.html"
|
||||
})
|
||||
})
|
||||
17
api-samples/identity/manifest.json
Executable file
17
api-samples/identity/manifest.json
Executable file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Identity API Sample",
|
||||
"version": "4.0",
|
||||
"manifest_version": 3,
|
||||
"key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCDJB6ZGcGxtlr/34s+TKgi84QiP7DMekqOjSUS2ubmbhchlM6CN9gYdGQ1aBI3TBXG3YaAu+XyutFA8M8NLLWc4OOGByWaGV11DP6p67g8a+Ids/gX6cNSRnRHiDZXAd44ATxoN4OZjZJk9iQ26RIUjwX07bzntlI+frwwKCk4WQIDAQAB",
|
||||
"action": {},
|
||||
"background": {
|
||||
"service_worker": "main.js"
|
||||
},
|
||||
"permissions": ["identity"],
|
||||
"oauth2": {
|
||||
// client_id below is specific to the application key. Follow the
|
||||
// documentation to obtain one for your app.
|
||||
"client_id": "497291774654.apps.googleusercontent.com",
|
||||
"scopes": ["https://www.googleapis.com/auth/userinfo.profile"]
|
||||
}
|
||||
}
|
||||
98
api-samples/identity/style.css
Normal file
98
api-samples/identity/style.css
Normal file
@@ -0,0 +1,98 @@
|
||||
html, body {
|
||||
margin: 0;
|
||||
font-family: 'Open Sans Regular', sans;
|
||||
color: #666;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
padding: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
.header hr {
|
||||
height: 6px;
|
||||
border: none;
|
||||
margin-top: 20px;
|
||||
background: -webkit-linear-gradient(0deg,
|
||||
rgb(51,105,232) 0%,
|
||||
rgb(51,105,232) 25%,
|
||||
rgb(222,30,37) 25%,
|
||||
rgb(222,30,37) 50%,
|
||||
rgb(255, 210, 0) 50%,
|
||||
rgb(255, 210, 0) 75%,
|
||||
rgb(76,187,71) 75%,
|
||||
rgb(76,187,71) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.header .links * {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.header .links a {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.header:hover .links a {
|
||||
color: #3399cc;
|
||||
}
|
||||
|
||||
.flows h2 {
|
||||
margin-left: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.flow {
|
||||
margin: 10px 20px 20px 20px;
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.flow button {
|
||||
cursor: pointer;
|
||||
background: -webkit-linear-gradient(top,#008dfd 0,#0370ea 100%);
|
||||
border: 1px solid #076bd2;
|
||||
text-shadow: 1px 1px 1px #076bd2;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
margin: .5em 0 1em;
|
||||
padding: 8px 17px 8px 17px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.flow button.error {
|
||||
background: -webkit-linear-gradient(top,#008dfd 0,#0370ea 100%);
|
||||
border: 1px solid #076bd2;
|
||||
text-shadow: 1px 1px 1px #076bd2;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
margin: .5em 0 1em;
|
||||
padding: 8px 17px 8px 17px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.flow button:disabled {
|
||||
background: -webkit-linear-gradient(top, #dcdcdc 0, #fafafa 100%);
|
||||
color: #999;
|
||||
text-shadow: 1px 1px 1px #fafafa;
|
||||
border: 1px solid #ddd;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.log {
|
||||
margin: 10px 20px 20px 20px;
|
||||
}
|
||||
|
||||
.log textarea {
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
margin: 2px;
|
||||
background: #FFFFFF;
|
||||
color: #727272;
|
||||
border: 1px solid rgb(182, 182, 182);
|
||||
}
|
||||
Reference in New Issue
Block a user