-
-
Save ansarizafar/761d3ca8d1d29d9c5efe6d183839c665 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { create, Header, Payload } from "https://deno.land/x/[email protected]/mod.ts"; | |
// License: MIT | |
// How to get an access token from Google Cloud using Deno | |
// | |
// Usage: | |
// const token = await getAccessToken(config) | |
type Config = { | |
privateKey: string; // Private base64 encoded RSA key, starts with -----BEGIN PRIVATE KEY----- | |
account: string; // Your Google service account ([email protected]) | |
scope: string; // Comma separated OAuth scopes to request (e.g. https://www.googleapis.com/auth/devstorage.read_write) | |
}; | |
const TOKEN_EXPIRATION_DELTA = 60 * 5; // Request a new token 5 mins before current expires | |
// Cache JWT & Access Token at file level to reduce fetch time | |
let cachedKey: CryptoKey | null = null; | |
let cachedJwt: string | null = null; | |
let cachedAccessToken: string | null = null; | |
let jwtExpiration = -1; | |
let accessTokenExpiration = -1; | |
function keyToArrayBuffer(privateKey: string): Uint8Array { | |
const cleanKey = privateKey.replaceAll("\\n", "").replaceAll("\n", "") | |
.replace( | |
"-----BEGIN PRIVATE KEY-----", | |
"", | |
).replace("-----END PRIVATE KEY-----", ""); | |
const byteString = atob(cleanKey); | |
const byteArray = new Uint8Array(byteString.length); | |
for (let i = 0; i < byteString.length; i++) { | |
byteArray[i] = byteString.charCodeAt(i); | |
} | |
return byteArray; | |
} | |
// Base64 private key string --> CryptoKey | |
async function getPrivateKey(privateKey: string): Promise<CryptoKey> { | |
if (cachedKey) { | |
return cachedKey; | |
} | |
const privateKeyBuffer = keyToArrayBuffer(privateKey); | |
cachedKey = await crypto.subtle.importKey( | |
"pkcs8", | |
privateKeyBuffer, | |
{ | |
name: "RSASSA-PKCS1-v1_5", | |
hash: { name: "SHA-256" }, | |
}, | |
true, | |
["sign"], | |
); | |
return cachedKey; | |
} | |
// Create JSON Web Token from config | |
async function getJWT(config: Config): Promise<string> { | |
const now = Math.floor(Date.now() / 1000); | |
if (cachedJwt && jwtExpiration > now - TOKEN_EXPIRATION_DELTA) { | |
return cachedJwt; | |
} | |
const key = await getPrivateKey(config.privateKey); | |
const header: Header = { "alg": "RS256", "typ": "JWT" }; | |
const claims: Payload = { | |
"iss": config.account, | |
"scope": config.scope, | |
"aud": "https://oauth2.googleapis.com/token", | |
"exp": now + 3600, | |
"iat": now, | |
}; | |
cachedJwt = await create(header, claims, key); | |
jwtExpiration = now + 3600; | |
return cachedJwt; | |
} | |
// Creates a CryptoKey, JSON Web Token (JWT), and then | |
// uses the JWT to request an access token from Google OAuth service | |
// or retrieves from cache if possible | |
async function getAccessToken(config: Config): Promise<string | null> { | |
const now = Math.floor(Date.now() / 1000); | |
if ( | |
cachedAccessToken && accessTokenExpiration > now - TOKEN_EXPIRATION_DELTA | |
) { | |
return cachedAccessToken; | |
} | |
try { | |
const jwt = await getJWT(config); | |
const response = await fetch( | |
"https://oauth2.googleapis.com/token", | |
{ | |
method: "POST", | |
headers: { | |
"Content-Type": "application/x-www-form-urlencoded", | |
}, | |
body: new URLSearchParams({ | |
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer", | |
assertion: jwt, | |
}), | |
}, | |
).then((response) => response.json()); | |
cachedAccessToken = response.access_token; | |
accessTokenExpiration = now + response.expires_in; | |
return cachedAccessToken; | |
} catch (_) { | |
// Handle errors here. | |
return null; | |
} | |
} | |
export { getAccessToken }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment