Saltar al contenido

Autorización de usuarios

Actúa en nombre de una persona con OAuth 2.0 Authorization Code y PKCE.

Usa este flujo cuando tu aplicación actúa en nombre de una persona: inicia sesión en Ecomiq y autoriza los permisos en la tienda de la aplicación. Si un servidor actúa como la aplicación, usa client_credentials desde Autenticación.

Usuario
Tu aplicación
Auth
Admin API
1Genera el PKCE
2Redirige a /connect/authorize
Inicia sesión y autoriza
Vuelve a redirect_uri con code
3POST /connect/token con code_verifier
access_token y refresh_token
4Llamada con Bearer
5POST /connect/token con refresh_token
Tokens nuevos
La tienda y los permisos autorizados quedan en el token.

Requisitos

  • Crea la aplicación en el panel con la conexión Con inicio de sesión y sus URL de retorno. Se comparan exactas.
  • La aplicación ya puede renovar el acceso: pide el scope offline_access para recibir un refresh token.
  • Una aplicación del panel solo se autoriza en la tienda donde se creó.
  • Las apps móviles y de navegador marcan Sin servidor: son clientes públicos y no llevan client_secret.

1. Genera el PKCE

PKCE es obligatorio y solo acepta S256. Guarda verifier y state en la sesión del usuario.

pkce.ts
import { createHash, randomBytes } from "node:crypto";

export const verifier = randomBytes(32).toString("base64url");
export const challenge = createHash("sha256").update(verifier).digest("base64url");
export const state = randomBytes(16).toString("base64url");

2. Redirige a authorize

URL de autorización
https://auth.ecomiq.pe/connect/authorize
  ?response_type=code
  &client_id=<client-id>
  &redirect_uri=https://tu-app.com/oauth/callback
  &scope=api_admin%20offline_access
  &state=<state>
  &code_challenge=<challenge>
  &code_challenge_method=S256
ParámetroUso
response_typeSiempre code.
redirect_uriUna de las URL registradas, idéntica.
scopeapi_admin para Admin API; offline_access para refresh token.
stateValor aleatorio; compáralo al volver.
code_challenge_methodSolo S256.
promptlogin obliga a iniciar sesión de nuevo.

La persona inicia sesión, elige la tienda y ve los permisos de tu aplicación. Al aceptar, vuelve a tu redirect_uri con code y state.

3. Canjea el código

El código sirve una vez y vence a los 5 minutos.

curl -X POST https://auth.ecomiq.pe/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=<code>" \
  -d "redirect_uri=https://tu-app.com/oauth/callback" \
  -d "code_verifier=$VERIFIER" \
  -d "client_id=$ECOMIQ_CLIENT_ID" \
  -d "client_secret=$ECOMIQ_CLIENT_SECRET"
Respuesta
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "CfDJ8...",
  "scope": "api_admin offline_access"
}

Un cliente público omite client_secret.

4. Llama a Admin API

Envía el token en Authorization: Bearer. La tienda sale del token; si envías x-Store-Id, debe coincidir o responde 403. Los permisos son los de tu aplicación que la persona todavía tiene en esa tienda.

5. Renueva el token

El access token dura 1 hora y el refresh token 14 días. Cada renovación devuelve un refresh token nuevo: guarda siempre el último.

curl -X POST https://auth.ecomiq.pe/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=$REFRESH_TOKEN" \
  -d "client_id=$ECOMIQ_CLIENT_ID" \
  -d "client_secret=$ECOMIQ_CLIENT_SECRET"

La renovación falla con invalid_grant si la persona cambió su contraseña, perdió acceso a la tienda o la aplicación se eliminó. Vuelve al paso 2.

Ejemplo completo

Un servidor Node.js sin dependencias: /login redirige a Ecomiq y /oauth/callback valida state y canjea el código.

server.mjs
import { createServer } from "node:http";
import { createHash, randomBytes } from "node:crypto";

const AUTH = "https://auth.ecomiq.pe";
const REDIRECT_URI = "https://tu-app.com/oauth/callback";
const pending = new Map();

createServer(async (req, res) => {
  const url = new URL(req.url, REDIRECT_URI);

  if (url.pathname === "/login") {
    const verifier = randomBytes(32).toString("base64url");
    const state = randomBytes(16).toString("base64url");
    pending.set(state, verifier);

    const authorize = new URL("/connect/authorize", AUTH);
    authorize.search = new URLSearchParams({
      response_type: "code",
      client_id: process.env.ECOMIQ_CLIENT_ID,
      redirect_uri: REDIRECT_URI,
      scope: "api_admin offline_access",
      state,
      code_challenge: createHash("sha256").update(verifier).digest("base64url"),
      code_challenge_method: "S256",
    }).toString();

    res.writeHead(302, { Location: authorize.toString() }).end();
    return;
  }

  if (url.pathname === "/oauth/callback") {
    const state = url.searchParams.get("state");
    const verifier = pending.get(state);
    pending.delete(state);
    if (!verifier || url.searchParams.has("error")) {
      res.writeHead(400).end(url.searchParams.get("error") ?? "invalid_state");
      return;
    }

    const response = await fetch(`${AUTH}/connect/token`, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({
        grant_type: "authorization_code",
        code: url.searchParams.get("code"),
        redirect_uri: REDIRECT_URI,
        code_verifier: verifier,
        client_id: process.env.ECOMIQ_CLIENT_ID,
        client_secret: process.env.ECOMIQ_CLIENT_SECRET,
      }),
    });
    const tokens = await response.json();
    // Guarda tokens.access_token y tokens.refresh_token en tu base de datos.

    res.writeHead(200).end("Tienda conectada.");
    return;
  }

  res.writeHead(404).end();
}).listen(3000);

Errores

DóndeerrorCausa
authorizeaccess_deniedLa persona rechazó o no puede autorizar en esa tienda.
authorizeinvalid_scopePediste un scope que la aplicación no tiene.
authorizeinvalid_requestFalta un parámetro, la URL de retorno o la tienda no sirven.
tokeninvalid_grantCódigo vencido o usado, code_verifier incorrecto o acceso revocado.

Última actualización:

En esta página