Skip to content

Instantly share code, notes, and snippets.

@orgsofthq
Last active January 14, 2022 05:42
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save orgsofthq/fe23f32230612efca3f3ed59e113994c to your computer and use it in GitHub Desktop.
Save orgsofthq/fe23f32230612efca3f3ed59e113994c to your computer and use it in GitHub Desktop.
import { create, Header, Payload } from "https://deno.land/x/djwt@v2.4/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 (...@...iam.gserviceaccount.com)
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