Last active
March 20, 2025 19:22
-
-
Save markelliot/6627143be1fc8209c9662c504d0ff205 to your computer and use it in GitHub Desktop.
Converts Google service user OAuth2 credentials into an access token in Cloudflare-compatible JS
This file contains hidden or 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
/** | |
* Get a Google auth token given service user credentials. This function | |
* is a very slightly modified version of the one found at | |
* https://community.cloudflare.com/t/example-google-oauth-2-0-for-service-accounts-using-cf-worker/258220 | |
* | |
* @param {string} user the service user identity, typically of the | |
* form [user]@[project].iam.gserviceaccount.com | |
* @param {string} key the private key corresponding to user | |
* @param {string} scope the scopes to request for this token, a | |
* listing of available scopes is provided at | |
* https://developers.google.com/identity/protocols/oauth2/scopes | |
* @returns a valid Google auth token for the provided service user and scope or undefined | |
*/ | |
async function getGoogleAuthToken(user, key, scope) { | |
function objectToBase64url(object) { | |
return arrayBufferToBase64Url( | |
new TextEncoder().encode(JSON.stringify(object)), | |
) | |
} | |
function arrayBufferToBase64Url(buffer) { | |
return btoa(String.fromCharCode(...new Uint8Array(buffer))) | |
.replace(/=/g, "") | |
.replace(/\+/g, "-") | |
.replace(/\//g, "_") | |
} | |
function str2ab(str) { | |
const buf = new ArrayBuffer(str.length); | |
const bufView = new Uint8Array(buf); | |
for (let i = 0, strLen = str.length; i < strLen; i++) { | |
bufView[i] = str.charCodeAt(i); | |
} | |
return buf; | |
}; | |
async function sign(content, signingKey) { | |
const buf = str2ab(content); | |
const plainKey = signingKey | |
.replace("-----BEGIN PRIVATE KEY-----", "") | |
.replace("-----END PRIVATE KEY-----", "") | |
.replace(/(\r\n|\n|\r)/gm, ""); | |
const binaryKey = str2ab(atob(plainKey)); | |
const signer = await crypto.subtle.importKey( | |
"pkcs8", | |
binaryKey, | |
{ | |
name: "RSASSA-PKCS1-V1_5", | |
hash: { name: "SHA-256" } | |
}, | |
false, | |
["sign"] | |
); | |
const binarySignature = await crypto.subtle.sign({ name: "RSASSA-PKCS1-V1_5" }, signer, buf); | |
return arrayBufferToBase64Url(binarySignature); | |
} | |
const jwtHeader = objectToBase64url({ alg: "RS256", typ: "JWT" }); | |
try { | |
const assertiontime = Math.round(Date.now() / 1000) | |
const expirytime = assertiontime + 3600 | |
const claimset = objectToBase64url({ | |
"iss": user, | |
"scope": scope, | |
"aud": "https://oauth2.googleapis.com/token", | |
"exp": expirytime, | |
"iat": assertiontime | |
}) | |
const jwtUnsigned = jwtHeader + "." + claimset | |
const signedJwt = jwtUnsigned + "." + (await sign(jwtUnsigned, key)) | |
const body = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + signedJwt; | |
const response = await fetch("https://oauth2.googleapis.com/token", { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/x-www-form-urlencoded", | |
"Cache-Control": "no-cache", | |
"Host": "oauth2.googleapis.com" | |
}, | |
body: body | |
}); | |
const oauth = await response.json(); | |
return oauth.access_token; | |
} catch (err) { | |
console.log(err) | |
} | |
} |
is this still functional getting a 1042 error for some reason
is this still functional getting a 1042 error for some reason
Try this:https://ryan-schachte.com/blog/oauth_cloudflare_workers/
@Schachte false flag this seems to be working again, wasnt working for a brief while because of the cloudflare --remote service went down.
Amazing folks, thanks for putting this together. Saved my butt!
Not sure why none of the other code in the thread works, here's something that works for me -- a modification of everything above.
Just call:
getGoogleAuthToken({private_key: 'SET_THIS_SECRET', client_email: 'SET_THIS_SECRET'}, ['ADD-YOUR-SCOPES'])
Using scopes: https://developers.google.com/identity/protocols/oauth2/scopes
// Inspiration: https://gist.github.com/markelliot/6627143be1fc8209c9662c504d0ff205
const { subtle } = globalThis.crypto;
const PEM_HEADER = '-----BEGIN PRIVATE KEY-----';
const PEM_FOOTER = '-----END PRIVATE KEY-----';
function objectToBase64url(object: object): string {
return arrayBufferToBase64Url(new TextEncoder().encode(JSON.stringify(object)) as unknown as ArrayBuffer);
}
function arrayBufferToBase64Url(buffer: ArrayBuffer) {
return btoa(String.fromCharCode(...new Uint8Array(buffer)))
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
}
function str2ab(str: string) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i += 1) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
async function sign(content: string, signingKey: string) {
const buf = str2ab(content);
const plainKey = signingKey
.replace(/(\r\n|\n|\r)/gm, '')
.replace(/\\n/g, '')
.replace(PEM_HEADER, '')
.replace(PEM_FOOTER, '')
.trim();
const binaryKey = str2ab(atob(plainKey));
const signer = await subtle.importKey(
'pkcs8',
binaryKey,
{
name: 'RSASSA-PKCS1-V1_5',
hash: { name: 'SHA-256' },
},
false,
['sign'],
);
const binarySignature = await subtle.sign({ name: 'RSASSA-PKCS1-V1_5' }, signer, buf);
return arrayBufferToBase64Url(binarySignature);
}
export async function getGoogleAuthToken(
credentials: {
private_key: string;
client_email: string;
},
scopes: string[],
) {
const { client_email: user, private_key: key } = credentials;
const scope = scopes.join(' ');
const jwtHeader = objectToBase64url({ alg: 'RS256', typ: 'JWT' });
try {
const assertiontime = Math.round(Date.now() / 1000);
const expirytime = assertiontime + 3600;
const claimset = objectToBase64url({
iss: user,
scope,
aud: 'https://oauth2.googleapis.com/token',
exp: expirytime,
iat: assertiontime,
});
const jwtUnsigned = `${jwtHeader}.${claimset}`;
const signedJwt = `${jwtUnsigned}.${await sign(jwtUnsigned, key)}`;
const body = `grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=${signedJwt}`;
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Cache-Control': 'no-cache',
Host: 'oauth2.googleapis.com',
},
body,
});
const resp = (await response.json()) as { access_token: string };
return resp.access_token;
} catch (e) {
console.error(e);
throw e;
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Love you all tyty