diff --git a/bin/interactive-login.js b/bin/interactive-login.js index 4d35533..0b8f268 100644 --- a/bin/interactive-login.js +++ b/bin/interactive-login.js @@ -1,12 +1,21 @@ import { loginInteractive } from "../src/auth.js"; import { config } from "../public-config.js"; + const scopes = ["https://management.azure.com/.default"]; -const token = await loginInteractive({ - tenantId: config.tenantId, - clientId: config.clientId, - scopes, -}); +let token; + +try { + token = await loginInteractive({ + tenantId: config.tenantId, + clientId: config.clientId, + scopes, + }); +} catch (e) { + console.error("Login failed:", e); + console.error(e.stack); + process.exit(1); +} console.log("Access token acquired:"); -console.log(token.accessToken); +//console.log(token.accessToken); diff --git a/src/auth.js b/src/auth.js index e755037..31f56cd 100644 --- a/src/auth.js +++ b/src/auth.js @@ -4,6 +4,30 @@ import { URL } from "node:url"; import open from "open"; import crypto from "node:crypto"; import { PublicClientApplication } from "@azure/msal-node"; +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; + +function fileCachePlugin(cachePath) { + return { + beforeCacheAccess: async (ctx) => { + if (fs.existsSync(cachePath)) { + ctx.tokenCache.deserialize( + fs.readFileSync(cachePath, "utf8") + ); + } + }, + afterCacheAccess: async (ctx) => { + if (ctx.cacheHasChanged) { + fs.mkdirSync(path.dirname(cachePath), { recursive: true }); + fs.writeFileSync( + cachePath, + ctx.tokenCache.serialize() + ); + } + }, + }; +} function generatePkce() { const verifier = crypto.randomBytes(32).toString("base64url"); // 43 chars, valid @@ -21,13 +45,36 @@ export async function loginInteractive({ tenantId, clientId, scopes }) { if (!Array.isArray(scopes) || scopes.length === 0) throw new Error("scopes[] is required"); + const cachePath = path.join( + os.homedir(), + ".config/azure-node-playground", + `${clientId}-token-cache.json` + ); + const pca = new PublicClientApplication({ auth: { clientId, authority: `https://login.microsoftonline.com/${tenantId}`, }, + cache: { + cachePlugin: fileCachePlugin(cachePath), + }, }); + const accounts = await pca.getTokenCache().getAllAccounts(); + + if (accounts.length > 0) { + try { + const silentResult = await pca.acquireTokenSilent({ + account: accounts[0], + scopes, + }); + return silentResult; + } catch (e) { + // proceed to interactive login + } + } + const pkce = generatePkce(); return new Promise((resolve, reject) => {