Instalação Completa do Ambiente React Native

Projeto: Federal-Pro

Expo SDK 57
1
Criar a Pasta do Projeto
Diretório

Crie a pasta Federal-Pro e entre no diretório antes de inicializar a estrutura.

mkdir Federal-Pro && cd Federal-Pro
2
Inicializar o Projeto (Expo SDK 57)
Inicialização

Gere a estrutura inicial apontando para a versão SDK 57 do Expo no diretório corrente.

npx create-expo-app@sdk-57 .
3
Navegação e Suporte a Safe Area (Android/iOS)
Navegação

Instale as bibliotecas de navegação e componentes para ajuste de tela e status bar no Android.

npm install @react-navigation/native @react-navigation/native-stack
npx expo install react-native-screens react-native-safe-area-context
4
Google Fonts e Carregamento de Fontes
Fontes

Suporte nativo para gerenciar e carregar fontes personalizadas do Google no dispositivo Android/iOS.

npx expo install expo-font @expo-google-fonts/inter
5
Font Awesome 5, Material Symbols e SVG
Ícones

Pacotes para suporte nativo a FontAwesome 5 (via vector-icons) e Google Material Symbols (via SVG).

npx expo install @expo/vector-icons react-native-svg
npm install material-symbols
6
Estilização (NativeWind / Tailwind ou Styled Components)
Estilos

Instalação dos mecanismos de estilo (classes utilitárias ou CSS-in-JS).

npm install nativewind tailwindcss react-native-reanimated styled-components
7
Utilitários e Armazenamento Local
Dados / HTTP

Banco de dados local (AsyncStorage) e cliente HTTP (Axios) para consumo de APIs.

npx expo install @react-native-async-storage/async-storage
npm install axios
8
Executar no Android / Expo Go
Execução

Inicie o servidor Metro Bundler. Pressione a no terminal para abrir no emulador Android ou leia o QR Code com o app Expo Go no celular.

npx expo start
9
Instalação e Arquivo Unificado JWT Backend (PHP)
Backend / PHP

Instalação da biblioteca no Composer, criação da tabela de blacklist MySQLi e arquivo completo do backend (jwt_system.php) para tratamento de login, refresh token, revogação e middleware global de proteção.

1. Instalação da dependência PHP via Composer:

composer require firebase/php-jwt

2. Tabela MySQLi para Blacklist e Revogação:

CREATE TABLE IF NOT EXISTS `jwt_blacklist` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `token_hash` VARCHAR(32) NOT NULL UNIQUE,
  `data_expiracao` DATETIME NOT NULL,
  `criado_em` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  INDEX (`token_hash`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

3. Código do backend PHP unificado (jwt_system.php):

<?php
/**
 * SISTEMA COMPLETO DE AUTENTICAÇÃO JWT
 * Arquivo unificado com suporte a Login, Geração, Validação, Refresh Token e Middleware Global.
 */

require_once __DIR__ . '/vendor/autoload.php';

use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;

// 1. CONFIGURAÇÕES GERAIS E CONEXÃO COM O BANCO
define('JWT_SECRET', 'SUA_CHAVE_SECRETA_EXTREMAMENTE_SEGURA_E_LONGA_12345');
define('JWT_ALGO', 'HS256');
define('JWT_EXPIRATION', 3600); // 1 hora de validade para o access token
define('JWT_REFRESH_EXPIRATION', 86400 * 7); // 7 dias para o refresh token

// Conexão MySQLi
$conn = new mysqli("localhost", "valorp3497_adm", "vp@140698", "valorp3497_adm");

if ($conn->connect_error) {
    die(json_encode(['status' => 'erro', 'mensagem' => 'Falha na conexão com o banco de dados.']));
}

// 2. FUNÇÕES AUXILIARES DE JWT E SEGURANÇA
function gerarTokensJWT($id_usuario, $email_usuario) {
    $agora = time();
    $ip_cliente = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? 'desconhecido';

    $payloadAccess = [
        'iat' => $agora,
        'exp' => $agora + JWT_EXPIRATION,
        'uid' => $id_usuario,
        'email' => $email_usuario,
        'ip' => $ip_cliente,
        'ua' => md5($user_agent),
        'tipo' => 'access'
    ];

    $payloadRefresh = [
        'iat' => $agora,
        'exp' => $agora + JWT_REFRESH_EXPIRATION,
        'uid' => $id_usuario,
        'tipo' => 'refresh'
    ];

    return [
        'access_token' => JWT::encode($payloadAccess, JWT_SECRET, JWT_ALGO),
        'refresh_token' => JWT::encode($payloadRefresh, JWT_SECRET, JWT_ALGO)
    ];
}

function validarJWT($token, $tipoEsperado = 'access') {
    global $conn;

    try {
        $decoded = JWT::decode($token, JWT_SECRET, [new Key(JWT_SECRET, JWT_ALGO)]);
        $dados = (array) $decoded;

        if (($dados['tipo'] ?? '') !== $tipoEsperado) {
            return ['valido' => false, 'erro' => 'Tipo de token inválido.'];
        }

        if ($tipoEsperado === 'access') {
            $ua_atual = md5($_SERVER['HTTP_USER_AGENT'] ?? 'desconhecido');
            if ($dados['ua'] !== $ua_atual) {
                return ['valido' => false, 'erro' => 'Ambiente de acesso alterado.'];
            }
        }

        $stmt = $conn->prepare("SELECT id FROM jwt_blacklist WHERE token_hash = ?");
        $hash = md5($token);
        $stmt->bind_param("s", $hash);
        $stmt->execute();
        if ($stmt->get_result()->num_rows > 0) {
            return ['valido' => false, 'erro' => 'Token revogado.'];
        }

        return ['valido' => true, 'dados' => $dados];

    } catch (ExpiredException $e) {
        return ['valido' => false, 'erro' => 'Token expirado.'];
    } catch (SignatureInvalidException $e) {
        return ['valido' => false, 'erro' => 'Assinatura de token inválida.'];
    } catch (Exception $e) {
        return ['valido' => false, 'erro' => 'Token inválido.'];
    }
}

function definirCookieJWT($nome, $token, $tempoExpiracao) {
    setcookie($nome, $token, [
        'expires' => time() + $tempoExpiracao,
        'path' => '/',
        'secure' => isset($_SERVER['HTTPS']),
        'httponly' => true,
        'samesite' => 'Lax'
    ]);
}

function revogarJWT($token) {
    global $conn;
    $hash = md5($token);
    $expiracao = time() + JWT_REFRESH_EXPIRATION;
    
    $stmt = $conn->prepare("INSERT IGNORE INTO jwt_blacklist (token_hash, data_expiracao) VALUES (?, FROM_UNIXTIME(?))");
    $stmt->bind_param("si", $hash, $expiracao);
    $stmt->execute();
}

// 3. PROCESSAMENTO DAS AÇÕES DO SISTEMA (LOGIN, REFRESH, LOGOUT)
$acao = $_GET['action'] ?? $_POST['action'] ?? null;

if ($acao === 'login') {
    header('Content-Type: application/json');
    
    $email = trim($_POST['email'] ?? '');
    $senha = trim($_POST['senha'] ?? '');

    if (empty($email) || empty($senha)) {
        http_response_code(400);
        echo json_encode(['status' => 'erro', 'mensagem' => 'Preencha todos os campos.']);
        exit;
    }

    $stmt = $conn->prepare("SELECT id, email, senha FROM usuarios WHERE email = ?");
    $stmt->bind_param("s", $email);
    $stmt->execute();
    $res = $stmt->get_result();

    if ($user = $res->fetch_assoc()) {
        if (password_verify($senha, $user['senha'])) {
            $tokens = gerarTokensJWT($user['id'], $user['email']);

            definirCookieJWT('auth_token', $tokens['access_token'], JWT_EXPIRATION);
            definirCookieJWT('refresh_token', $tokens['refresh_token'], JWT_REFRESH_EXPIRATION);

            echo json_encode([
                'status' => 'sucesso',
                'mensagem' => 'Login realizado com sucesso.',
                'access_token' => $tokens['access_token'],
                'refresh_token' => $tokens['refresh_token']
            ]);
            exit;
        }
    }

    http_response_code(401);
    echo json_encode(['status' => 'erro', 'mensagem' => 'Credenciais incorretas.']);
    exit;
}

if ($acao === 'refresh') {
    header('Content-Type: application/json');

    $refreshToken = $_COOKIE['refresh_token'] ?? $_POST['refresh_token'] ?? null;

    if (!$refreshToken) {
        http_response_code(400);
        echo json_encode(['status' => 'erro', 'mensagem' => 'Refresh token não fornecido.']);
        exit;
    }

    $validacao = validarJWT($refreshToken, 'refresh');

    if ($validacao['valido']) {
        $uid = $validacao['dados']['uid'];

        $stmt = $conn->prepare("SELECT id, email FROM usuarios WHERE id = ?");
        $stmt->bind_param("i", $uid);
        $stmt->execute();
        $user = $stmt->get_result()->fetch_assoc();

        if ($user) {
            $novosTokens = gerarTokensJWT($user['id'], $user['email']);

            revogarJWT($refreshToken);

            definirCookieJWT('auth_token', $novosTokens['access_token'], JWT_EXPIRATION);
            definirCookieJWT('refresh_token', $novosTokens['refresh_token'], JWT_REFRESH_EXPIRATION);

            echo json_encode([
                'status' => 'sucesso',
                'access_token' => $novosTokens['access_token'],
                'refresh_token' => $novosTokens['refresh_token']
            ]);
            exit;
        }
    }

    http_response_code(401);
    echo json_encode(['status' => 'erro', 'mensagem' => 'Refresh token inválido ou expirado.']);
    exit;
}

if ($acao === 'logout') {
    $token = $_COOKIE['auth_token'] ?? null;
    $refreshToken = $_COOKIE['refresh_token'] ?? null;

    if ($token) revogarJWT($token);
    if ($refreshToken) revogarJWT($refreshToken);

    setcookie('auth_token', '', time() - 3600, '/');
    setcookie('refresh_token', '', time() - 3600, '/');

    if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
        header('Content-Type: application/json');
        echo json_encode(['status' => 'sucesso', 'mensagem' => 'Logout efetuado.']);
        exit;
    }

    header('Location: login.php');
    exit;
}

// 4. MIDDLEWARE GLOBAL DE PROTEÇÃO DO SISTEMA
$token = null;

$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? $headers['authorization'] ?? '';
if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
    $token = $matches[1];
}

if (!$token && isset($_COOKIE['auth_token'])) {
    $token = $_COOKIE['auth_token'];
}

$checagem = $token ? validarJWT($token, 'access') : ['valido' => false, 'erro' => 'Token ausente.'];

if (!$checagem['valido']) {
    if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
        header('Content-Type: application/json');
        http_response_code(401);
        echo json_encode([
            'status' => 'erro',
            'mensagem' => $checagem['erro'],
            'exige_refresh' => ($checagem['erro'] === 'Token expirado.')
        ]);
        exit;
    }

    header('Location: login.php');
    exit;
}

$usuario_logado_id = $checagem['dados']['uid'];
$usuario_logado_email = $checagem['dados']['email'];