Skip to main content

Gerador de Link para Login Externo (Magic Link)

Login Externo v2 — Guia de Integração (Cliente)

> Documento para o cliente implementar a geração do magic link em seu próprio sistema. > Versão do esquema: v2. Validade do link: 2 minutos.

Visão geral

O Login Externo permite que o seu sistema (intranet, portal etc.) gere um link de acesso que autentica automaticamente uma pessoa na plataforma, sem digitar senha.


O que você recebe da nossa equipe

Entregues uma única vez, por canal seguro:

Valor O que é Onde usar
external_login_id Chave pública (identificador da empresa) Vai dentro do link. Não é segredo.
external_login_secret Chave privada (segredo de assinatura) Fica só no seu servidor. Nunca vai no link.
url_empresa URL base da sua plataforma (ex.: https://sua-empresa.exemplo.com) Prefixo do link. Sem barra final.

> ⚠️ O external_login_secret é secreto. Guarde-o apenas no servidor (variável de ambiente ou configuração protegida). Nunca o coloque em código versionado público, no navegador, em logs ou na URL. Se suspeitar de vazamento, solicite a rotação do segredo à nossa equipe.


GET {url_empresa}/login_externo/acessar?v=2&d={payload}&s={assinatura}
  • v=2 — versão do esquema (fixo).
  • d — o payload em Base64 url-safe sem padding (JSON abaixo).
  • s — a assinatura HMAC-SHA256 do valor de d, em hexadecimal.

Payload (JSON antes de codificar em d)

{
  &quot;emp&quot;"emp":   &quot;&amp;lt;"<external_login_id&amp;gt;&quot;>",
  &quot;login&quot;"login": &quot;&amp;lt;"<login_da_pessoa&amp;gt;&quot;>",
  &quot;ts&quot;"ts":    1753800000
}
  • emp — a sua chave pública (external_login_id).
  • login — o login da pessoa na plataforma. Se for CPF, envie apenas os números (sem pontos ou traço).
  • ts — o timestamp Unix (segundos) do momento da geração. O servidor aceita uma janela de ±120 segundos.

Algoritmo (agnóstico de linguagem)

  1. Monte o objeto JSON com emp, login e ts (timestamp Unix em segundos).
  2. Serialize esse JSON em texto (UTF-8).
  3. Codifique em Base64 url-safe: Base64 padrão, trocando + por - e / por _, e removendo o = de padding do final. O resultado é o valor d.
  4. Calcule s = HMAC-SHA256(mensagem = d, chave = external_login_secret) e represente em hexadecimal minúsculo.
  5. Monte a URL: {url_empresa}/login_externo/acessar?v=2&amp;amp;d={d}&amp;amp;s={s}.

&gt; 🔑 Ponto crítico: a assinatura é calculada sobre a string d (exatamente como ela vai na URL) — não sobre o JSON original. Isso significa que pequenas diferenças na serialização do JSON entre linguagens não quebram a validação, desde que você assine exatamente o mesmo d que envia na URL.

&gt; &gt;

d e s usam apenas caracteres seguros para URL (A–Z a–z 0–9 - _ e hexadecimal), então não precisam de URL-encoding adicional.


Exemplos de código

Em todos: gere o link no momento do redirecionamento (ele expira em 2 minutos) e substitua os placeholders pelos valores recebidos.

PHP

&amp;lt;<?php
    $externalLoginId     = &#039;&amp;lt;'<external_login_id&amp;gt;&#039;>';      // chave PÚBLICA (vai na URL)
    $externalLoginSecret = &#039;&amp;lt;'<external_login_secret&amp;gt;&#039;>';  // chave PRIVADA — nunca expor
    $urlEmpresa          = &#039;'https://&amp;lt;<url-empresa&amp;gt;&#039;>';    // sem barra final
    $loginPessoa         = &#039;&amp;lt;'<login_da_pessoa&amp;gt;&#039;>';         // CPF: só números

    $payload = array(
        &#039;emp&#039;'emp'   =&amp;gt;> $externalLoginId,
        &#039;login&#039;'login' =&amp;gt;> $loginPessoa,
        &#039;ts&#039;'ts'    =&amp;gt;> time(),
    );

    // Base64 url-safe sem padding
    $d = rtrim(strtr(base64_encode(json_encode($payload)), &#039;'+/&#039;', &#039;'-_&#039;_'), &#039;'=&#039;');

    // HMAC-SHA256 (hex) sobre o valor de $d
    $s = hash_hmac(&#039;sha256&#039;'sha256', $d, $externalLoginSecret);

    echo $urlEmpresa . &#039;'/login_externo/acessar?v=2&amp;amp;d=&#039;' . $d . '&#039;&amp;amp;s=&#039;' . $s;
?&amp;gt;>

Java

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class MagicLinkV2 {
    public static String gerar(String externalLoginId, String externalLoginSecret,
                               String urlEmpresa, String loginPessoa) throws Exception {

        long ts = System.currentTimeMillis() / 1000L;

        // Monte o JSON (use uma lib JSON se o login puder conter caracteres especiais)
        String json = &quot;"{\&quot;"emp\&quot;":\&quot;&quot;"" + externalLoginId + &quot;"\&quot;",\&quot;"login\&quot;":\&quot;&quot;"" + loginPessoa + &quot;"\&quot;",\&quot;"ts\&quot;":&quot;" + ts + &quot;"}&quot;";

        // Base64 url-safe sem padding
        String d = Base64.getUrlEncoder().withoutPadding()
                .encodeToString(json.getBytes(StandardCharsets.UTF_8));

        // HMAC-SHA256 (hex) sobre o valor de d
        Mac mac = Mac.getInstance(&quot;HmacSHA256&quot;"HmacSHA256");
        mac.init(new SecretKeySpec(externalLoginSecret.getBytes(StandardCharsets.UTF_8), &quot;HmacSHA256&quot;"HmacSHA256"));
        byte[] raw = mac.doFinal(d.getBytes(StandardCharsets.UTF_8));

        StringBuilder hex = new StringBuilder(raw.length * 2);
        for (byte b : raw) hex.append(String.format(&quot;"%02x&quot;02x", b));
        String s = hex.toString();

        return urlEmpresa + &quot;"/login_externo/acessar?v=2&amp;amp;d=&quot;" + d + "&quot;&amp;amp;s=&quot;" + s;
    }
}

Node.js

const crypto = require(&#039;crypto&#039;'crypto');

function gerarLink(externalLoginId, externalLoginSecret, urlEmpresa, loginPessoa) {
    const payload = {
        emp:   externalLoginId,
        login: loginPessoa,
        ts:    Math.floor(Date.now() / 1000),
    };

    // Base64 url-safe sem padding (Node 16+ suporta &#039;base64url&#039;'base64url')
    const d = Buffer.from(JSON.stringify(payload)).toString(&#039;base64url&#039;'base64url');

    // HMAC-SHA256 (hex) sobre o valor de d
    const s = crypto.createHmac(&#039;sha256&#039;'sha256', externalLoginSecret).update(d).digest(&#039;hex&#039;'hex');

    return `${urlEmpresa}/login_externo/acessar?v=2&amp;amp;d=${d}&amp;amp;s=${s}`;
}

&gt; Em versões antigas do Node (sem &#039;base64url&#039;'base64url'), gere assim: &gt; Buffer.from(JSON.stringify(payload)).toString(&#039;base64&#039;'base64').replace(/\+/g,&#039;'-&#039;').replace(/\//g,&#039;_&#039;'_').replace(/=+$/,&#039;&#039;'')

Python

import base64, hmac, hashlib, json, time

def gerar_link(external_login_id, external_login_secret, url_empresa, login_pessoa):
    payload = {&quot;emp&quot;"emp": external_login_id, &quot;login&quot;"login": login_pessoa, &quot;ts&quot;"ts": int(time.time())}

    # JSON compacto (sem espaços) em UTF-8
    raw = json.dumps(payload, separators=(&quot;",&quot;", &quot;":&quot;")).encode(&quot;"utf-8&quot;8")

    # Base64 url-safe sem padding
    d = base64.urlsafe_b64encode(raw).rstrip(b&quot;b"=&quot;").decode(&quot;ascii&quot;"ascii")

    # HMAC-SHA256 (hex) sobre o valor de d
    s = hmac.new(external_login_secret.encode(&quot;"utf-8&quot;8"),
                 d.encode(&quot;"utf-8&quot;8"), hashlib.sha256).hexdigest()

    return f&quot;f"{url_empresa}/login_externo/acessar?v=2&amp;amp;d={d}&amp;amp;s={s}&quot;"

Regras operacionais


Testando

Erros comuns

Sintoma Causa provável
Link rejeitado logo após gerar ts em milissegundos em vez de segundos; ou relógio do servidor defasado.
Sempre "acesso inválido" Assinatura calculada sobre o JSON em vez do valor d; ou external_login_secret incorreto.
Funciona intermitentemente Link pré-gerado/cacheado sendo aberto após os 2 minutos.
Caracteres estranhos na URL d gerado em Base64 padrão (com +, /, =) em vez de url-safe sem padding.
Pessoa não encontrada login divergente do cadastro (ex.: CPF com formatação, ou pessoa inativa).

Dúvidas

Em caso de dúvida na integração, entre em contato com a nossa equipe. Para rotação do segredo (troca do external_login_secret), solicite formalmente — a rotação invalida os links assinados com o segredo anterior.