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.2

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)

Alternativa ao fluxo OAuth com redirecionamento: obtenha um Bearer token diretamente com usuário e senha (endpoint mobile). O mesmo accessToken retornado é usado em getUserInfo, refreshToken e logoutWithAccessToken.

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);   // Bearer token
  console.log(tokens.refreshToken);  // pode ser undefined
  console.log(tokens.expiresIn);     // em segundos
  console.log(tokens.tokenType);     // "Bearer"
} catch (error) {
  if (error instanceof GamNeedsSecondFactorError) {
    // GAM pediu segundo fator (TOTP): chame novamente com password = código
  } else if (error instanceof GamNeedsOtpError) {
    // GAM enviou OTP por e-mail: chame novamente com password = código
  } else if (error instanceof GamTokenError) {
    console.error("Erro ao obter token:", error.message);
  }
}

Se o GAM exigir 2FA ou OTP por e-mail, a primeira chamada retorna erro (202); use o código recebido e chame getAccessTokenWithCredentials de novo 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

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.

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,
} 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
    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.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.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) diretamente com usuário e senha, sem fluxo OAuth. Endpoint mobile: POST /oauth/access_token. Útil para aplicações que autenticam no backend (ex.: BFF). Suporta 2FA e OTP por e-mail (segundo passo com código).

Parâmetros:

  • options.username (string, obrigatório): Nome de usuário
  • options.password (string, obrigatório): Senha (ou código OTP/TOTP no segundo passo)
  • options.scope (string, opcional): Escopo (padrão: valor de defaultScopes do client, ex. "gam_user_data")
  • options.grantType (string, opcional): Tipo de grant (padrão: "GAMLocal")
  • options.repository (string, opcional): Nome do repositório GAM (enviado em additional_parameters como Repository)
  • options.additionalParameters (object, opcional): Parâmetros extras no body (ex. 2FA: OTPStep: "2", UseTwoFactorAuthentication: "true")

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

Erros: Em 202 com código 410 lança GamNeedsSecondFactorError (pedir TOTP); em 202 com código 400 lança GamNeedsOtpError (pedir código por e-mail). Chame novamente com password: codigo 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".

Endpoints GAM

O módulo usa os seguintes endpoints do GAM:

  • Authorization: /oauth/gam/signin
  • Token (Web): /oauth/gam/access_token
  • Token (mobile/credenciais): /oauth/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: