> ## Documentation Index
> Fetch the complete documentation index at: https://docs.contabull.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Assinatura de Requisições

> Como assinar requisições de API usando autenticação JWT

# Assinatura de Requisições

Nossa API utiliza assinatura de requisições baseada em JWT para autenticar e verificar a integridade das suas requisições. Este guia explica como implementar a assinatura de requisições em suas aplicações.

## Visão Geral

Cada requisição para nossa API deve incluir um token JWT assinado no cabeçalho Authorization. O token contém declarações sobre a requisição e é assinado usando sua chave privada com o algoritmo RS256.

<Steps>
  <Step title="Preparar credenciais">
    Você precisa de duas credenciais:

    * **Chave de API**: Seu identificador único
    * **Chave Privada**: Sua chave privada RSA em formato PEM
  </Step>

  <Step title="Extrair caminho da URL">
    Extraia o caminho e os parâmetros de consulta da URL da requisição.
    Não inclua o domínio ou protocolo nos dados assinados.
  </Step>

  <Step title="Gerar hash do corpo">
    Calcule um hash SHA-256 do corpo da requisição para garantir integridade.
  </Step>

  <Step title="Criar e assinar JWT">
    Crie um JWT com declarações específicas e assine-o com sua chave privada.
  </Step>

  <Step title="Adicionar cabeçalho Authorization">
    Inclua o JWT assinado no cabeçalho Authorization como um token Bearer.
  </Step>
</Steps>

## Implementação

Abaixo está uma implementação em TypeScript que você pode usar como referência para outras linguagens:

```typescript
import * as jwt from 'jsonwebtoken';
import * as crypto from 'crypto';
import { URL } from 'url';

/**
 * Assina uma requisição de API com JWT usando algoritmo RS256
 * @param privateKey Sua chave privada RSA
 * @param apiKey Sua chave de API
 * @param requestUrl URL completa da requisição
 * @param requestBody Corpo da requisição (string ou undefined)
 * @returns String do token Bearer para o cabeçalho Authorization
 */
export function signRequest(
  privateKey: string,
  apiKey: string,
  requestUrl: string,
  requestBody?: string
): string {
  // Construir url da requisição - extrair caminho e consulta
  const parsedUrl = new URL(requestUrl);
  const url = parsedUrl.pathname + parsedUrl.search;

  // Hash do corpo da requisição
  const body = requestBody || '{}';
  const bodyHash = crypto
    .createHash('sha256')
    .update(body)
    .digest('hex');

  // Gerar o JWT assinado
  const jwtHeader = {
    typ: 'JWT',
    alg: 'RS256'
  };

  const now = Math.floor(Date.now() / 1000);

  const jwtData = {
    uri: url,
    iat: now,
    exp: now + 55,
    sub: apiKey,
    bodyHash: bodyHash
  };

  const signedJwt = jwt.sign(jwtData, privateKey, {
    algorithm: 'RS256',
    header: jwtHeader
  });

  // Retorna valor do cabeçalho Authorization
  return `Bearer ${signedJwt}`;
}
```

## Estrutura do Payload JWT

O payload JWT deve conter as seguintes declarações:

<ResponseField name="uri" type="string" required>
  O caminho da URL com parâmetros de consulta (excluindo o domínio).
  Exemplo: `/v1/resources?filter=active`
</ResponseField>

<ResponseField name="iat" type="number" required>
  Horário de emissão como timestamp Unix (segundos desde a época).
</ResponseField>

<ResponseField name="exp" type="number" required>
  Horário de expiração como timestamp Unix. Deve ser definido como `iat + 55` segundos.
</ResponseField>

<ResponseField name="sub" type="string" required>
  Sua chave de API.
</ResponseField>

<ResponseField name="bodyHash" type="string" required>
  Hash SHA-256 do corpo da requisição como uma string hexadecimal.
</ResponseField>

## Implementações em Linguagens Específicas

<Tabs>
  <Tab title="Node.js">
    ```javascript
    const jwt = require('jsonwebtoken');
    const crypto = require('crypto');
    const { URL } = require('url');

    function signRequest(privateKey, apiKey, requestUrl, requestBody) {
      // Extrair caminho com consulta
      const parsedUrl = new URL(requestUrl);
      const url = parsedUrl.pathname + parsedUrl.search;

      // Hash do corpo
      const body = requestBody || '{}';
      const bodyHash = crypto.createHash('sha256').update(body).digest('hex');

      // Criar JWT
      const now = Math.floor(Date.now() / 1000);
      const payload = {
        uri: url,
        iat: now,
        exp: now + 55,
        sub: apiKey,
        bodyHash: bodyHash
      };

      const signedJwt = jwt.sign(payload, privateKey, {
        algorithm: 'RS256',
        header: { typ: 'JWT', alg: 'RS256' }
      });

      return `Bearer ${signedJwt}`;
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python
    import time
    import hashlib
    from urllib.parse import urlparse
    import jwt

    def sign_request(private_key, api_key, request_url, request_body=None):
        # Extrair caminho com consulta
        parsed_url = urlparse(request_url)
        url = parsed_url.path
        if parsed_url.query:
            url += '?' + parsed_url.query
        
        # Hash do corpo
        body = request_body if request_body else '{}'
        body_hash = hashlib.sha256(body.encode('utf-8')).hexdigest()
        
        # Criar JWT
        now = int(time.time())
        payload = {
            'uri': url,
            'iat': now,
            'exp': now + 55,
            'sub': api_key,
            'bodyHash': body_hash
        }
        
        headers = {
            'typ': 'JWT',
            'alg': 'RS256'
        }
        
        signed_jwt = jwt.encode(
            payload=payload,
            key=private_key,
            algorithm='RS256',
            headers=headers
        )
        
        return f"Bearer {signed_jwt}"
    ```
  </Tab>

  <Tab title="Java">
    ```java
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import io.jsonwebtoken.security.Keys;
    import java.net.URI;
    import java.net.URL;
    import java.nio.charset.StandardCharsets;
    import java.security.KeyFactory;
    import java.security.MessageDigest;
    import java.security.PrivateKey;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.time.Instant;
    import java.util.Base64;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;

    public String signRequest(String privateKeyPem, String apiKey, String requestUrl, String requestBody) throws Exception {
        // Analisar URL
        URL url = new URL(requestUrl);
        String path = url.getPath();
        String query = url.getQuery();
        String uriPath = query != null ? path + "?" + query : path;
        
        // Hash do corpo
        String body = requestBody != null ? requestBody : "{}";
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = digest.digest(body.getBytes(StandardCharsets.UTF_8));
        String bodyHash = bytesToHex(hash);
        
        // Hora atual
        long now = Instant.now().getEpochSecond();
        
        // Cabeçalho JWT
        Map<String, Object> header = new HashMap<>();
        header.put("typ", "JWT");
        header.put("alg", "RS256");
        
        // Payload JWT
        Map<String, Object> claims = new HashMap<>();
        claims.put("uri", uriPath);
        claims.put("iat", now);
        claims.put("exp", now + 55);
        claims.put("sub", apiKey);
        claims.put("bodyHash", bodyHash);
        
        // Converter PEM para PrivateKey
        PrivateKey privateKey = loadPrivateKey(privateKeyPem);
        
        // Assinar JWT
        String signedJwt = Jwts.builder()
            .setHeader(header)
            .setClaims(claims)
            .signWith(privateKey, SignatureAlgorithm.RS256)
            .compact();
        
        return "Bearer " + signedJwt;
    }

    private static String bytesToHex(byte[] hash) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : hash) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        return hexString.toString();
    }

    private static PrivateKey loadPrivateKey(String privateKeyPem) throws Exception {
        // Remover cabeçalhos e converter para binário
        String privateKeyContent = privateKeyPem
            .replace("-----BEGIN PRIVATE KEY-----", "")
            .replace("-----END PRIVATE KEY-----", "")
            .replaceAll("\\s", "");
        
        byte[] encoded = Base64.getDecoder().decode(privateKeyContent);
        
        // Criar chave privada
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
        return keyFactory.generatePrivate(keySpec);
    }
    ```
  </Tab>

  <Tab title="PHP">
    ```php
    <?php
    use \Firebase\JWT\JWT;

    function signRequest($privateKey, $apiKey, $requestUrl, $requestBody = null) {
        // Analisar URL
        $urlParts = parse_url($requestUrl);
        $path = $urlParts['path'] ?? '';
        $query = isset($urlParts['query']) ? '?' . $urlParts['query'] : '';
        $url = $path . $query;
        
        // Hash do corpo
        $body = $requestBody ?? '{}';
        $bodyHash = hash('sha256', $body);
        
        // Hora atual
        $now = time();
        
        // Cabeçalhos JWT
        $header = [
            'typ' => 'JWT',
            'alg' => 'RS256'
        ];
        
        // Payload JWT
        $payload = [
            'uri' => $url,
            'iat' => $now,
            'exp' => $now + 55,
            'sub' => $apiKey,
            'bodyHash' => $bodyHash
        ];
        
        // Assinar JWT
        $signedJwt = JWT::encode($payload, $privateKey, 'RS256', null, $header);
        
        return 'Bearer ' . $signedJwt;
    }
    ?>
    ```
  </Tab>
</Tabs>

## Testando Sua Implementação

Você pode verificar se sua implementação está funcionando corretamente:

1. Gerando uma requisição assinada
2. Decodificando o JWT em [jwt.io](https://jwt.io/) para verificar as declarações
3. Fazendo uma requisição de teste para nosso endpoint de API

<Warning>
  Certifique-se de que o corpo da requisição seja idêntico entre a geração da assinatura e a requisição real.
  Mesmo uma única diferença de espaço em branco resultará em um hash diferente e falha na validação da assinatura.
</Warning>

## Problemas Comuns

<AccordionGroup>
  <Accordion title="Falha na Verificação da Assinatura">
    Isso normalmente significa:

    * O formato da sua chave privada está incorreto
    * O hash do corpo não corresponde ao corpo da requisição real
    * O JWT expirou (deve ser válido apenas por 55 segundos)
  </Accordion>

  <Accordion title="Declarações Obrigatórias Ausentes">
    Certifique-se de que seu JWT inclua todas as declarações obrigatórias: `uri`, `iat`, `exp`, `sub` e `bodyHash`.
  </Accordion>

  <Accordion title="Formato de URL Incorreto">
    A declaração `uri` deve incluir apenas o caminho e a string de consulta, não o domínio ou protocolo.
  </Accordion>
</AccordionGroup>

## Melhores Práticas de Segurança

* Armazene sua chave privada com segurança e nunca a exponha em código do lado do cliente
* Gere um novo JWT para cada requisição
* Mantenha o tempo de expiração curto (55 segundos) para evitar ataques de repetição
* Implemente tratamento adequado de erros para falhas de autenticação
