0

Get Access Token (Service Account)

by
Published today

Mint a one-hour Admin SDK access token from a service account with domain-wide delegation, impersonating a Workspace admin. Returns `{ token, expires_in }`, which can be passed as the Google Workspace resource of any other script in a flow. The scopes must be authorized for the service account's client ID in Admin console > Security > API controls > Domain-wide delegation.

Script gworkspace Verified

The script

Submitted by hugo989 Typescript (fetch-only)
Verified 1 hour ago
1
//native
2

3
const b64url = (bytes: Uint8Array) =>
4
  btoa(String.fromCharCode(...bytes))
5
    .replace(/\+/g, "-")
6
    .replace(/\//g, "_")
7
    .replace(/=+$/, "")
8

9
/**
10
 * Get Access Token (Service Account)
11
 * Mint a one-hour Admin SDK access token from a service account with domain-wide delegation, impersonating a Workspace admin. Returns `{ token, expires_in }`, which can be passed as the Google Workspace resource of any other script in a flow. The scopes must be authorized for the service account's client ID in Admin console > Security > API controls > Domain-wide delegation.
12
 */
13
export async function main(
14
  service_account: RT.GcpServiceAccount,
15
  admin_email: string,
16
  scopes: string[] = [
17
    "https://www.googleapis.com/auth/admin.directory.user",
18
    "https://www.googleapis.com/auth/admin.directory.group",
19
    "https://www.googleapis.com/auth/admin.directory.orgunit",
20
  ],
21
) {
22
  const tokenUri = service_account.token_uri || "https://oauth2.googleapis.com/token"
23
  const iat = Math.floor(Date.now() / 1000)
24
  const enc = new TextEncoder()
25
  const unsigned = [
26
    { alg: "RS256", typ: "JWT" },
27
    {
28
      iss: service_account.client_email,
29
      sub: admin_email,
30
      scope: scopes.join(" "),
31
      aud: tokenUri,
32
      iat,
33
      exp: iat + 3600,
34
    },
35
  ]
36
    .map((part) => b64url(enc.encode(JSON.stringify(part))))
37
    .join(".")
38

39
  const pem = service_account.private_key
40
    .replace(/\\n/g, "\n")
41
    .replace(/-----[A-Z ]+-----/g, "")
42
    .replace(/\s/g, "")
43
  const key = await crypto.subtle.importKey(
44
    "pkcs8",
45
    Uint8Array.from(atob(pem), (c) => c.charCodeAt(0)),
46
    { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
47
    false,
48
    ["sign"],
49
  )
50
  const signature = new Uint8Array(
51
    await crypto.subtle.sign("RSASSA-PKCS1-v1_5", key, enc.encode(unsigned)),
52
  )
53

54
  const response = await fetch(tokenUri, {
55
    method: "POST",
56
    headers: {
57
      "Content-Type": "application/x-www-form-urlencoded",
58
      Accept: "application/json",
59
    },
60
    body: new URLSearchParams({
61
      grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
62
      assertion: `${unsigned}.${b64url(signature)}`,
63
    }),
64
  })
65

66
  if (!response.ok) {
67
    throw new Error(`${response.status} ${await response.text()}`)
68
  }
69

70
  const { access_token, expires_in } = (await response.json()) as {
71
    access_token: string
72
    expires_in: number
73
  }
74
  return { token: access_token, expires_in }
75
}
76