Autenticação

Refresh Token

Como renovar tokens de acesso automaticamente

O refresh token permite renovar access tokens automaticamente sem precisar fazer login novamente, mantendo a sessão ativa de forma contínua na API da Nuvende.

Como Funciona o Refresh Token

O refresh token na API da Nuvende é um mecanismo que permite renovar tokens de acesso expirados sem a necessidade de um novo login. Quando você faz refresh:

  1. Token Atual é Usado: O access token atual é enviado no header Authorization
  2. Novo Token é Gerado: A API retorna um novo access token válido
  3. Sessão Mantida: A sessão continua ativa sem interrupção
  4. Tempo de Vida: O novo token tem um novo tempo de expiração

Por que Usar Refresh Token?

  • Experiência do Usuário: Evita interrupções desnecessárias no fluxo de trabalho
  • Segurança: Tokens têm vida útil limitada, mas podem ser renovados de forma segura
  • Eficiência: Não precisa reautenticar constantemente
  • Conformidade: Segue padrões de segurança da indústria

Implementação

Endpoint Oficial

URL: POST /api/v2/auth/refresh
Ambiente Homologação: https://api-h.nuvende.com.br/api/v2/auth/refresh
Ambiente Produção: https://api.nuvende.com.br/api/v2/auth/refresh

Headers Obrigatórios

  • Authorization: Bearer {access_token} - Token de acesso atual (não expirado)

Exemplo de Requisição

curl -X POST https://api-h.nuvende.com.br/api/v2/auth/refresh \
  -H "Authorization: Bearer seu_access_token_atual"

Resposta de Sucesso

{
  "access_token": "novo_access_token_gerado",
  "token_type": "bearer",
  "expires_in": 3600
}

Explicação da Resposta:

  • access_token: Novo token de acesso válido
  • token_type: Tipo do token (sempre "bearer")
  • expires_in: Tempo de vida do novo token em segundos (3600 = 1 hora)

Implementação em Código

JavaScript/Node.js - Implementação Completa

Vamos integrar o refresh token à classe NuvendeAuth que criamos na página de login:

const axios = require('axios');

class NuvendeAuth {
  constructor(clientId, clientSecret, baseUrl) {
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.baseUrl = baseUrl;
    this.accessToken = null;
    this.tokenExpiry = null;
  }

  // ... outros métodos da classe (getAccessToken, logout, etc.) ...

  /**
   * Renova o access token usando o token atual
   * IMPORTANTE: Na API da Nuvende, o refresh usa o access token atual, não um refresh token separado
   */
  async refreshAccessToken() {
    // Verificar se temos um token para renovar
    if (!this.accessToken) {
      throw new Error('Nenhum access token disponível para renovação. Faça login primeiro.');
    }

    try {
      console.log('Renovando access token...');
      
      const response = await axios.post(`${this.baseUrl}/api/v2/auth/refresh`, {}, {
        headers: {
          'Authorization': `Bearer ${this.accessToken}`
        }
      });

      // Atualizar token e tempo de expiração
      this.accessToken = response.data.access_token;
      this.tokenExpiry = Date.now() + (response.data.expires_in * 1000);
      
      console.log(`Token renovado com sucesso. Expira em ${response.data.expires_in} segundos`);
      return this.accessToken;
      
    } catch (error) {
      console.error('Erro na renovação do token:', error.response?.data || error.message);
      
      // Se o refresh falhar, limpar tokens e forçar novo login
      this.clearTokens();
      throw error;
    }
  }

  /**
   * Obtém um access token válido
   * Se o token atual é válido, retorna ele
   * Se expirou, tenta renovar automaticamente
   */
  async getValidAccessToken() {
    // Verificar se o token ainda é válido (com margem de 5 minutos)
    const now = Date.now();
    const margin = 5 * 60 * 1000; // 5 minutos em millisegundos
    
    if (this.accessToken && this.tokenExpiry && (now + margin) < this.tokenExpiry) {
      console.log('Usando token existente');
      return this.accessToken;
    }

    // Se não temos token, fazer login
    if (!this.accessToken) {
      console.log('Nenhum token disponível, fazendo login...');
      return await this.getAccessToken();
    }

    // Se o token expirou, tentar renovar
    console.log('Token expirado, tentando renovar...');
    try {
      return await this.refreshAccessToken();
    } catch (error) {
      console.log('Falha na renovação, fazendo novo login...');
      return await this.getAccessToken();
    }
  }

  /**
   * Limpa todos os tokens armazenados
   */
  clearTokens() {
    this.accessToken = null;
    this.tokenExpiry = null;
    console.log('Tokens limpos');
  }
}

// Exemplo de uso prático
async function exemploRefresh() {
  const nuvende = new NuvendeAuth(
    process.env.NUVENDE_CLIENT_ID,
    process.env.NUVENDE_CLIENT_SECRET,
    'https://api-h.nuvende.com.br'
  );

  try {
    // Fazer login inicial
    await nuvende.getAccessToken();
    console.log('Login realizado');

    // Fazer algumas operações...
    console.log('Realizando operações...');

    // Simular expiração do token (em produção, isso acontece automaticamente)
    nuvende.tokenExpiry = Date.now() - 1000; // Forçar expiração

    // Tentar fazer uma operação (vai renovar automaticamente)
    const token = await nuvende.getValidAccessToken();
    console.log('Token renovado automaticamente:', token);

  } catch (error) {
    console.error('Erro:', error.message);
  }
}

Python - Implementação Completa

import requests
import time
from datetime import datetime, timedelta

class NuvendeAuth:
    def __init__(self, client_id, client_secret, base_url):
        self.client_id = client_id
        self.client_secret = client_secret
        self.base_url = base_url
        self.access_token = None
        self.token_expiry = None

    # ... outros métodos da classe (get_access_token, logout, etc.) ...

    def refresh_access_token(self):
        """
        Renova o access token usando o token atual
        IMPORTANTE: Na API da Nuvende, o refresh usa o access token atual, não um refresh token separado
        """
        if not self.access_token:
            raise Exception('Nenhum access token disponível para renovação. Faça login primeiro.')

        try:
            print('Renovando access token...')
            
            headers = {
                'Authorization': f'Bearer {self.access_token}'
            }
            
            response = requests.post(
                f"{self.base_url}/api/v2/auth/refresh",
                headers=headers
            )
            
            response.raise_for_status()
            data = response.json()
            
            # Atualizar token e tempo de expiração
            self.access_token = data['access_token']
            self.token_expiry = datetime.now() + timedelta(seconds=data['expires_in'])
            
            print(f"Token renovado com sucesso. Expira em {data['expires_in']} segundos")
            return self.access_token
            
        except requests.exceptions.RequestException as e:
            print(f"Erro na renovação do token: {e}")
            # Se o refresh falhar, limpar tokens e forçar novo login
            self.clear_tokens()
            raise

    def get_valid_access_token(self):
        """
        Obtém um access token válido
        Se o token atual é válido, retorna ele
        Se expirou, tenta renovar automaticamente
        """
        # Verificar se o token ainda é válido (com margem de 5 minutos)
        now = datetime.now()
        margin = timedelta(minutes=5)
        
        if (self.access_token and self.token_expiry and 
            (now + margin) < self.token_expiry):
            print('Usando token existente')
            return self.access_token

        # Se não temos token, fazer login
        if not self.access_token:
            print('Nenhum token disponível, fazendo login...')
            return self.get_access_token()

        # Se o token expirou, tentar renovar
        print('Token expirado, tentando renovar...')
        try:
            return self.refresh_access_token()
        except Exception as error:
            print('Falha na renovação, fazendo novo login...')
            return self.get_access_token()

    def clear_tokens(self):
        """
        Limpa todos os tokens armazenados
        """
        self.access_token = None
        self.token_expiry = None
        print('Tokens limpos')

# Exemplo de uso prático
async def exemplo_refresh():
    nuvende = NuvendeAuth(
        os.getenv('NUVENDE_CLIENT_ID'),
        os.getenv('NUVENDE_CLIENT_SECRET'),
        'https://api-h.nuvende.com.br'
    )

    try:
        # Fazer login inicial
        await nuvende.get_access_token()
        print('Login realizado')

        # Fazer algumas operações...
        print('Realizando operações...')

        # Simular expiração do token (em produção, isso acontece automaticamente)
        nuvende.token_expiry = datetime.now() - timedelta(seconds=1)

        # Tentar fazer uma operação (vai renovar automaticamente)
        token = await nuvende.get_valid_access_token()
        print('Token renovado automaticamente:', token)

    except Exception as error:
        print(f'Erro: {error}')

Renovação Automática e Interceptores

Interceptor Axios para Renovação Automática

Para aplicações que fazem muitas requisições, é útil implementar um interceptor que renova tokens automaticamente:

// Configurar interceptor global do Axios
axios.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;
    
    // Se recebeu 401 e ainda não tentou renovar
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      
      try {
        console.log('Token expirado, tentando renovar automaticamente...');
        
        // Renovar token
        await nuvende.refreshAccessToken();
        
        // Atualizar header da requisição original
        originalRequest.headers.Authorization = `Bearer ${nuvende.accessToken}`;
        
        // Refazer a requisição original
        return axios(originalRequest);
        
      } catch (refreshError) {
        console.error('Falha na renovação automática:', refreshError);
        
        // Se falhou, limpar tokens e redirecionar para login
        nuvende.clearTokens();
        clearStoredTokens();
        redirectToLogin();
        
        return Promise.reject(refreshError);
      }
    }
    
    return Promise.reject(error);
  }
);

Timer de Renovação Preventiva

Para aplicações de longa duração, implemente renovação preventiva:

class NuvendeAuth {
  constructor(clientId, clientSecret, baseUrl) {
    // ... código anterior ...
    this.refreshTimer = null;
  }

  /**
   * Inicia renovação automática preventiva
   * Renova o token 5 minutos antes de expirar
   */
  startAutoRefresh() {
    if (!this.tokenExpiry) {
      console.log('Nenhum token para renovar automaticamente');
      return;
    }

    // Calcular tempo para renovação (5 minutos antes da expiração)
    const refreshTime = this.tokenExpiry - Date.now() - (5 * 60 * 1000);
    
    if (refreshTime <= 0) {
      console.log('Token já próximo do vencimento, renovando imediatamente');
      this.refreshAccessToken().then(() => {
        this.startAutoRefresh(); // Agendar próxima renovação
      });
      return;
    }

    console.log(`Agendando renovação automática em ${Math.round(refreshTime / 1000)} segundos`);
    
    this.refreshTimer = setTimeout(async () => {
      try {
        await this.refreshAccessToken();
        this.startAutoRefresh(); // Agendar próxima renovação
      } catch (error) {
        console.error('Falha na renovação automática:', error);
        // Em caso de erro, tentar fazer login novamente
        try {
          await this.getAccessToken();
          this.startAutoRefresh();
        } catch (loginError) {
          console.error('Falha no login automático:', loginError);
        }
      }
    }, refreshTime);
  }

  /**
   * Para a renovação automática
   */
  stopAutoRefresh() {
    if (this.refreshTimer) {
      clearTimeout(this.refreshTimer);
      this.refreshTimer = null;
      console.log('Renovação automática parada');
    }
  }
}

Tratamento de Erros

Códigos de Resposta HTTP

CódigoSignificadoQuando OcorreComo Resolver
200OKToken renovado com sucesso-
401UnauthorizedToken inválido ou expiradoFazer login novamente
500Internal Server ErrorErro interno do servidorTentar novamente mais tarde

Implementação Robusta de Tratamento de Erros

class RefreshErrorHandler {
  static async handleRefreshError(error, nuvende) {
    const status = error.response?.status;
    const data = error.response?.data;
    
    switch (status) {
      case 401:
        console.error('❌ Token inválido ou expirado para renovação:', data);
        // Limpar tokens e forçar novo login
        nuvende.clearTokens();
        clearStoredTokens();
        this.redirectToLogin();
        break;
        
      case 500:
        console.error('❌ Erro interno do servidor na renovação:', data);
        // Tentar novamente após um tempo
        await this.retryRefresh(nuvende);
        break;
        
      default:
        console.error('❌ Erro inesperado na renovação:', error.message);
        // Limpar tokens e forçar novo login
        nuvende.clearTokens();
        clearStoredTokens();
        this.redirectToLogin();
    }
  }
  
  static async retryRefresh(nuvende, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
      try {
        console.log(`Tentativa ${i + 1} de renovação...`);
        await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); // Backoff linear
        await nuvende.refreshAccessToken();
        console.log('Renovação bem-sucedida após retry');
        return;
      } catch (error) {
        console.error(`Falha na tentativa ${i + 1}:`, error.message);
        if (i === maxRetries - 1) {
          console.error('Todas as tentativas de renovação falharam');
          nuvende.clearTokens();
          clearStoredTokens();
          this.redirectToLogin();
        }
      }
    }
  }
  
  static redirectToLogin() {
    // Implementar redirecionamento para página de login
    window.location.href = '/login';
  }
}

Cenários de Uso Comuns

1. Aplicação Web com Múltiplas Requisições

// Configurar interceptor global
axios.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 401 && !error.config._retry) {
      error.config._retry = true;
      
      try {
        await nuvende.refreshAccessToken();
        error.config.headers.Authorization = `Bearer ${nuvende.accessToken}`;
        return axios(error.config);
      } catch (refreshError) {
        RefreshErrorHandler.handleRefreshError(refreshError, nuvende);
        return Promise.reject(refreshError);
      }
    }
    return Promise.reject(error);
  }
);

2. Aplicação Desktop com Renovação Preventiva

// Iniciar renovação automática após login
async function iniciarAplicacao() {
  try {
    await nuvende.getAccessToken();
    nuvende.startAutoRefresh(); // Iniciar renovação automática
    console.log('Aplicação iniciada com renovação automática');
  } catch (error) {
    console.error('Erro ao iniciar aplicação:', error);
  }
}

// Parar renovação automática ao fechar aplicação
window.addEventListener('beforeunload', () => {
  nuvende.stopAutoRefresh();
});

3. Aplicação Mobile com Cache de Token

// Salvar token no storage local
function salvarToken(token, expiry) {
  localStorage.setItem('nuvende_token', token);
  localStorage.setItem('nuvende_token_expiry', expiry.toString());
}

// Carregar token do storage
function carregarToken() {
  const token = localStorage.getItem('nuvende_token');
  const expiry = localStorage.getItem('nuvende_token_expiry');
  
  if (token && expiry && Date.now() < parseInt(expiry)) {
    nuvende.accessToken = token;
    nuvende.tokenExpiry = parseInt(expiry);
    return true;
  }
  
  return false;
}

2025 © Nuvende - CNPJ 38.297.374/0001-93