npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gam-oauth-authenticator

v1.1.5

Published

Headless OAuth 2.0 client for GeneXus GAM Identity Provider

Readme

GAM OAuth Authenticator

Um módulo Node.js headless e independente de framework para integração OAuth 2.0 com GeneXus GAM (GeneXus Access Manager) como Identity Provider.

Características

  • ✅ Headless - sem dependências de frameworks HTTP (Express, Next.js, Fastify, etc.)
  • ✅ TypeScript nativo com tipos completos
  • ✅ Compatível com Node.js 20+ (usa fetch nativo)
  • ✅ Suporte a PKCE (Proof Key for Code Exchange)
  • ✅ Totalmente testável e configurável
  • ✅ Tratamento de erros explícito
  • ✅ Single Logout (SLO) – URL de logout único com o GAM

Instalação

npm install gam-oauth-authenticator

Requisitos

  • Node.js 20.0.0 ou superior

Uso Básico

Criar o Client

import { createGamOAuthClient } from "gam-oauth-authenticator";

const gamClient = createGamOAuthClient({
  baseUrl: "https://idp.exemplo.com/VirtualDir",
  clientId: "my-client-id",
  clientSecret: "my-client-secret",
  defaultScopes: ["gam_user_data"],
});

1. Obter access token (por credenciais)

Obtenha um Bearer token no endpoint oauth/gam/v2.0/access_token sem redirecionamento OAuth. O mesmo accessToken é usado em getUserInfo, refreshToken e logoutWithAccessToken.

Fluxos suportados: usuário/senha (GAMLocal), OTP (One Time Password), API Key e 2FA (segundo fator) via additionalParameters.

Usuário e senha (GAMLocal)

try {
  const tokens = await gamClient.getAccessTokenWithCredentials({
    username: "usuario",
    password: "senha",
    scope: "gam_user_data", // opcional
    grantType: "GAMLocal",   // opcional, padrão GAMLocal
    // repository: "MeuRepo", // opcional, para GAM multi-repositório
  });
  console.log(tokens.accessToken);
} catch (error) {
  if (error instanceof GamNeedsSecondFactorError) {
    // 2FA: chame novamente com password = código TOTP e additionalParameters (OTPStep "2", etc.)
  } else if (error instanceof GamNeedsOtpError) {
    // OTP por e-mail ou 401 OTP step 1: chame com otpStep: 2 e password = código
  }
}

OTP (One Time Password)

Dois passos: (1) solicitar envio do código; (2) enviar o código para obter o token. O GAM envia o código por e-mail ou SMS (configurável). Documentação: GAM - One Time Password (OTP).

// Passo 1: solicitar código (GAM envia por e-mail/SMS). Resposta 401 com Code 400 → GamNeedsOtpError
try {
  await gamClient.getAccessTokenWithCredentials({
    username: "usuario",
    authenticationTypeName: "gam-otp-web", // nome do tipo OTP no GAM
    otpStep: 1,
  });
} catch (e) {
  if (e instanceof GamNeedsOtpError) {
    // Mostrar campo para o usuário informar o código recebido
  }
}

// Passo 2: enviar código recebido
const tokens = await gamClient.getAccessTokenWithCredentials({
  username: "usuario",
  password: "123456", // código OTP recebido
  authenticationTypeName: "gam-otp-web",
  otpStep: 2,
});

API Key

Autenticação com chave gerada no GAM para o usuário/aplicação. No fluxo API Key o username não é obrigatório. Documentação: HowTo: Use API Key to request services from an application.

const tokens = await gamClient.getAccessTokenWithCredentials({
  apiKey: "chave-api-gerada-no-gam",
  authenticationTypeName: "apikey", // opcional, padrão "apikey"
  scope: "gam_user_data",
  // username opcional no fluxo API Key
});

2FA (segundo fator)

Se o GAM exigir 2FA ou OTP por e-mail no fluxo GAMLocal, a primeira chamada retorna 202 com GamNeedsSecondFactorError ou GamNeedsOtpError. Chame novamente com password: codigo e, no 2FA, additionalParameters com OTPStep: "2" e UseTwoFactorAuthentication: "true".

2. Construir URL de Login (fluxo OAuth)

const signinUrl = gamClient.buildSigninUrl({
  redirectUri: "https://myapp.com/callback",
  state: "random-state-string",
  scopes: ["gam_user_data"], // opcional, usa defaultScopes se não fornecido
});

// Redirecionar o usuário para signinUrl

Quando o GAM tem múltiplos tipos de autenticação (ex.: Gov.br, Google, local), use authenticationTypeName para direcionar o usuário ao IdP desejado:

// Login com Gov.br
const signinUrlGovBr = gamClient.buildSigninUrl({
  redirectUri: "https://myapp.com/callback",
  state: "random-state-string",
  authenticationTypeName: "pmlink1_govbr", // nome configurado no GAM
});

// Login com Google
const signinUrlGoogle = gamClient.buildSigninUrl({
  redirectUri: "https://myapp.com/callback",
  state: "random-state-string",
  authenticationTypeName: "pmlink1_google",
});

3. Trocar Código por Token (fluxo OAuth)

try {
  const tokens = await gamClient.exchangeCodeForToken({
    code: "authorization-code-from-callback",
    redirectUri: "https://myapp.com/callback", // deve corresponder exatamente
  });

  console.log(tokens.accessToken);
  console.log(tokens.refreshToken); // pode ser undefined
  console.log(tokens.expiresIn); // em segundos
} catch (error) {
  if (error instanceof GamTokenError) {
    console.error("Erro OAuth:", error.error, error.errorDescription);
  }
}

4. Obter Informações do Usuário

try {
  const userInfo = await gamClient.getUserInfo(tokens.accessToken);

  console.log(userInfo.guid); // ID do usuário (GAM GUID)
  console.log(userInfo.name);
  console.log(userInfo.email);
  console.log(userInfo.roles);
} catch (error) {
  if (error instanceof GamUserInfoError) {
    console.error("Erro ao buscar informações do usuário:", error.message);
  }
}

5. Renovar Token (Opcional)

try {
  const newTokens = await gamClient.refreshToken(tokens.refreshToken);

  console.log(newTokens.accessToken);
  console.log(newTokens.refreshToken); // novo refresh token (pode ser undefined)
} catch (error) {
  if (error instanceof GamTokenError) {
    console.error("Erro ao renovar token:", error.message);
  }
}

6. Single Logout (Logout único)

Para encerrar a sessão no GAM e redirecionar o usuário de volta à sua aplicação, use a URL de signout. O GAM invalida a sessão do usuário no IdP e redireciona para o redirectUri informado. Recomenda-se validar o parâmetro state no callback (igual ao fluxo de login) para proteção CSRF.

import crypto from "crypto";

// 1. Gerar e guardar o state (ex.: em cookie ou sessão)
const state = crypto.randomBytes(32).toString("hex");

// 2. Construir a URL de logout e redirecionar o usuário
const signoutUrl = gamClient.buildSignoutUrl({
  redirectUri: "https://myapp.com/auth/logout/callback",
  token: tokens.accessToken, // access token da sessão atual
  state,
});

// Redirecionar o usuário para signoutUrl
// window.location.href = signoutUrl;  (no browser)
// res.redirect(signoutUrl);           (no Express, etc.)

No callback (/auth/logout/callback), valide que o state recebido na query corresponde ao que foi guardado; em seguida, limpe a sessão/local storage da sua aplicação e redirecione para a página inicial ou login.

7. Logout para token mobile (access token)

Para tokens obtidos via login por credenciais (getAccessTokenWithCredentials), use o endpoint de logout mobile para invalidar o access token no GAM sem redirecionamento. O serviço REST recebe o token no header Authorization e invalida a sessão no servidor.

Endpoint utilizado: POST /oauth/logout (baseUrl + path).

try {
  const result = await gamClient.logoutWithAccessToken(tokens.accessToken);

  if (result.code === "200") {
    // Logout concluído no GAM; limpe a sessão local (cookie, storage, etc.)
    // e redirecione ou responda ao cliente
  }
} catch (error) {
  if (error instanceof GamTokenError) {
    console.error("Erro ao invalidar token:", error.message);
  } else if (error instanceof GamNetworkError) {
    console.error("Erro de rede no logout:", error.message);
  }
}

Em aplicações BFF: ao receber uma requisição de logout do cliente, chame logoutWithAccessToken(accessToken) com o token da sessão atual e, em seguida, remova o cookie ou a sessão no seu backend.

Documentação oficial: HowTo: Sign out from mobile applications using GAM.

8. Validar permissões (External Authorization API)

É possível validar permissões do usuário através de uma API externa (por exemplo, uma API GeneXus que consome GAM). O client oferece dois métodos que chamam o endpoint POST /User/CheckPermissions dessa API, autenticando com o Bearer token do usuário.

Configuração: a URL base da API de permissões é opcional. Se não for informada em externalAuthorizationApiUrl, o módulo usa o mesmo baseUrl do GAM e assume o path /ExternalAuthorization (ou seja, baseUrl + "/ExternalAuthorization"). Assim, basta configurar baseUrl (por exemplo via GAM_BASE_URL) para que a validação de permissões use essa mesma base.

const gamClient = createGamOAuthClient({
  baseUrl: "https://idp.exemplo.com/VirtualDir",
  clientId: "my-client-id",
  clientSecret: "my-client-secret",
  // externalAuthorizationApiUrl opcional: padrão = baseUrl + "/ExternalAuthorization"
});

O pacote inclui o arquivo ExternalAuthorization.xpz, um objeto GeneXus de exemplo que expõe a API de validação de permissões (POST /User/CheckPermissions). Em aplicações GeneXus, você pode importar esse xpz para disponibilizar o endpoint esperado por checkPermission e checkPermissions (por exemplo em baseUrl + "/ExternalAuthorization").

Uma permissão — retorna true ou false:

try {
  const authorized = await gamClient.checkPermission(
    accessToken,
    "NomeDaPermissao",
    { applicationGUID: "guid-da-aplicacao" }
  );
  if (authorized) {
    // usuário tem a permissão
  }
} catch (error) {
  if (error instanceof GamPermissionCheckError) {
    console.error("Falha na API de permissões:", error.statusCode, error.responseBody);
  }
  if (error instanceof GamConfigurationError) {
    console.error("Erro de configuração:", error.message);
  }
}

Várias permissões — retorna a lista com o resultado de cada uma:

try {
  const result = await gamClient.checkPermissions(
    accessToken,
    ["Permissao1", "Permissao2"],
    { applicationGUID: "guid-da-aplicacao" }
  );
  for (const p of result.permissions) {
    console.log(p.permissionName, p.isAuthorized);
  }
  if (result.messages && result.messages.length > 0) {
    // mensagens retornadas pela API (opcional)
  }
} catch (error) {
  if (error instanceof GamPermissionCheckError) {
    console.error("Falha na API de permissões:", error.message);
  }
}

A API esperada recebe no body ApplicationGUID (opcional) e PermissionNames (array de strings) e retorna PermissionAuthorization.Permissions com PermissionName e IsAuthorized para cada item.

Exemplo de aplicação

Uma aplicação de exemplo que demonstra o uso do módulo com Express está disponível no repositório, na pasta examples/. A documentação de instalação, configuração e execução está em examples/README.md. O pacote publicado no npm não inclui essa pasta.

Uso com PKCE

import { createGamOAuthClient } from "gam-oauth-authenticator";
import crypto from "crypto";

// Gerar code verifier e challenge (você precisa implementar isso)
const codeVerifier = crypto.randomBytes(32).toString("base64url");
const codeChallenge = crypto
  .createHash("sha256")
  .update(codeVerifier)
  .digest("base64url");

// Construir URL com PKCE
const signinUrl = gamClient.buildSigninUrl({
  redirectUri: "https://myapp.com/callback",
  state: "random-state-string",
  pkce: {
    codeChallenge,
    method: "S256",
  },
});

// Ao trocar o código, incluir o code verifier
const tokens = await gamClient.exchangeCodeForToken({
  code: "authorization-code",
  redirectUri: "https://myapp.com/callback",
  codeVerifier, // necessário quando PKCE foi usado
});

Tratamento de Erros

O módulo exporta classes de erro específicas:

import {
  GamOAuthError,
  GamTokenError,
  GamUserInfoError,
  GamNetworkError,
  GamConfigurationError,
  GamPermissionCheckError,
  GamNeedsSecondFactorError,
  GamNeedsOtpError,
} from "gam-oauth-authenticator";

try {
  // ... operação OAuth
} catch (error) {
  if (error instanceof GamTokenError) {
    // Erro no token endpoint
    console.error(error.error); // código do erro OAuth
    console.error(error.errorDescription); // descrição do erro
    console.error(error.statusCode); // código HTTP
  } else if (error instanceof GamUserInfoError) {
    // Erro no userinfo endpoint
    console.error(error.statusCode);
    console.error(error.responseBody);
  } else if (error instanceof GamNetworkError) {
    // Erro de rede
    console.error(error.originalError);
  } else if (error instanceof GamConfigurationError) {
    // Erro de configuração (ex.: externalAuthorizationApiUrl ausente)
    console.error(error.message);
  } else if (error instanceof GamPermissionCheckError) {
    // Erro na API de permissões (External Authorization)
    console.error(error.statusCode, error.responseBody);
  } else if (error instanceof GamNeedsSecondFactorError) {
    // 2FA: chamar novamente com password = código TOTP e additionalParameters
    console.error(error.message);
  } else if (error instanceof GamNeedsOtpError) {
    // OTP: passo 1 (código enviado) ou 202; chamar com otpStep: 2 e password = código
    console.error(error.message);
  }
}

API Reference

createGamOAuthClient(config)

Cria uma instância do client OAuth GAM.

Parâmetros:

  • config.baseUrl (string, obrigatório): URL base do Identity Provider GAM
  • config.clientId (string, obrigatório): ID do cliente OAuth
  • config.clientSecret (string, obrigatório): Secret do cliente OAuth
  • config.defaultScopes (string[], opcional): Escopos padrão (padrão: ["gam_user_data"])
  • config.externalAuthorizationApiUrl (string, opcional): URL base da API External Authorization; se omitido, usa baseUrl + "/ExternalAuthorization"
  • config.fetch (Function, opcional): Implementação customizada de fetch (útil para testes)

Retorna: GamOAuthClient

GamOAuthClient.buildSigninUrl(options)

Constrói a URL de autorização para login.

Parâmetros:

  • options.redirectUri (string, obrigatório): URI de redirecionamento
  • options.state (string, obrigatório): Parâmetro state para proteção CSRF
  • options.scopes (string[], opcional): Escopos a solicitar
  • options.authenticationTypeName (string, opcional): Nome do tipo de autenticação no GAM (ex.: Gov.br, Google); obrigatório quando há múltiplos tipos configurados
  • options.pkce (object, opcional): Configuração PKCE
    • pkce.codeChallenge (string): Code challenge
    • pkce.method ("S256"): Método do challenge

Retorna: string - URL completa de autorização

GamOAuthClient.buildSignoutUrl(options)

Constrói a URL de signout (single logout) no GAM. O usuário deve ser redirecionado para essa URL; o GAM encerra a sessão e redireciona para redirectUri. O state deve ser guardado (ex.: cookie) e validado no callback.

Parâmetros:

  • options.redirectUri (string, obrigatório): URI para onde o usuário será redirecionado após o logout no GAM
  • options.token (string, obrigatório): Access token da sessão atual (enviado ao GAM para invalidar a sessão)
  • options.state (string, obrigatório): Valor de state para proteção CSRF (guardar e validar no callback)

Retorna: string - URL completa de signout

GamOAuthClient.exchangeCodeForToken(options)

Troca um código de autorização por tokens.

Parâmetros:

  • options.code (string, obrigatório): Código de autorização
  • options.redirectUri (string, obrigatório): URI de redirecionamento usado na autorização
  • options.codeVerifier (string, opcional): Code verifier para PKCE

Retorna: Promise<TokenResponse>

GamOAuthClient.getAccessTokenWithCredentials(options)

Obtém um access token (Bearer) no endpoint POST oauth/gam/v2.0/access_token sem redirecionamento. Suporta: usuário/senha (GAMLocal), OTP, API Key e 2FA.

Parâmetros:

  • options.username (string, opcional): Nome de usuário (obrigatório em OTP e fluxo por senha; opcional em API Key)
  • options.password (string, opcional): Senha (obrigatória em GAMLocal e OTP step 2; omitida em API Key e OTP step 1)
  • options.scope (string, opcional): Escopo (padrão: defaultScopes do client)
  • options.grantType (string, opcional): Grant type (padrão: "GAMLocal"; OTP/API Key usam "password" internamente)
  • options.repository (string, opcional): Repositório GAM (em additional_parameters.Repository)
  • options.authenticationTypeName (string, opcional): Nome do tipo de autenticação no GAM; obrigatório para OTP; para API Key o padrão é "apikey"
  • options.otpStep (1 | 2, opcional): OTP: 1 = solicitar código; 2 = enviar código (password = código)
  • options.apiKey (string, opcional): API Key gerada no GAM; quando informada, não se envia password
  • options.additionalParameters (object, opcional): Parâmetros extras (ex. 2FA: OTPStep: "2", UseTwoFactorAuthentication: "true")

Retorna: Promise<TokenResponse>accessToken, refreshToken, expiresIn, tokenType.

Erros: 401 com Code 400 (OTP step 1) ou 202 com código 400 → GamNeedsOtpError; 202 com código 410 → GamNeedsSecondFactorError. Chame novamente com o código (password) e, no 2FA, additionalParameters adequados.

GamOAuthClient.getUserInfo(accessToken)

Obtém informações do usuário autenticado.

Parâmetros:

  • accessToken (string, obrigatório): Access token

Retorna: Promise<UserInfoResponse>

GamOAuthClient.refreshToken(refreshToken, options?)

Renova um access token usando um refresh token.

Parâmetros:

  • refreshToken (string, obrigatório): Refresh token
  • options.useMobileEndpoint (boolean, opcional): Se true, usa o endpoint /oauth/access_token (para tokens obtidos via getAccessTokenWithCredentials)

Retorna: Promise<RefreshTokenResponse>

GamOAuthClient.logoutWithAccessToken(accessToken)

Invalida o access token no servidor GAM (fluxo mobile; não redireciona o usuário). Use para tokens obtidos via getAccessTokenWithCredentials.

Parâmetros:

  • accessToken (string, obrigatório): Access token obtido no login mobile (credenciais)

Retorna: Promise<LogoutResponse> – objeto com code (string); sucesso quando code === "200".

GamOAuthClient.checkPermission(accessToken, permissionName, options)

Valida uma permissão do usuário via API External Authorization (GeneXus). Usa baseUrl + "/ExternalAuthorization" quando externalAuthorizationApiUrl não é informada.

Parâmetros:

  • accessToken (string, obrigatório): Access token do usuário
  • permissionName (string, obrigatório): Nome da permissão
  • options (obrigatório): { applicationGUID: string } — GUID da aplicação (conforme contrato da API)

Retorna: Promise<boolean>true se autorizado, false caso contrário.

Erros: GamPermissionCheckError em falha da API (ex.: 404, resposta inválida).

GamOAuthClient.checkPermissions(accessToken, permissionNames, options)

Valida várias permissões do usuário em uma única chamada à API External Authorization.

Parâmetros:

  • accessToken (string, obrigatório): Access token do usuário
  • permissionNames (string[], obrigatório): Array com os nomes das permissões
  • options (obrigatório): { applicationGUID: string } — GUID da aplicação

Retorna: Promise<CheckPermissionsResult>{ permissions: Array<{ permissionName, isAuthorized }>, messages?: GeneXusMessage[] }.

Erros: Os mesmos de checkPermission.

Endpoints GAM

O módulo usa os seguintes endpoints do GAM:

  • Authorization: /oauth/gam/signin
  • Token (Web): /oauth/gam/access_token
  • Token (credenciais/OTP/API Key): /oauth/gam/v2.0/access_token
  • Logout mobile: POST /oauth/logout – invalida o access token no GAM (uso com token obtido via credenciais)
  • UserInfo: /oauth/gam/userinfo
  • Signout (Single Logout): /oauth/gam/signout

Documentação oficial: