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.
/ connect/ authorizeredirect_uri con codePOST / connect/ token con code_verifieraccess_token y refresh_tokenBearerPOST / connect/ token con refresh_tokenRequisitos
- 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_accesspara 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.
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
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ámetro | Uso |
|---|---|
response_type | Siempre code. |
redirect_uri | Una de las URL registradas, idéntica. |
scope | api_admin para Admin API; offline_access para refresh token. |
state | Valor aleatorio; compáralo al volver. |
code_challenge_method | Solo S256. |
prompt | login 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"{
"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: / redirige a Ecomiq y
/ valida state y canjea el código.
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ónde | error | Causa |
|---|---|---|
| authorize | access_denied | La persona rechazó o no puede autorizar en esa tienda. |
| authorize | invalid_scope | Pediste un scope que la aplicación no tiene. |
| authorize | invalid_request | Falta un parámetro, la URL de retorno o la tienda no sirven. |
| token | invalid_grant | Código vencido o usado, code_verifier incorrecto o acceso revocado. |
Última actualización: