<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>OAuth2.0 on kenji.blog</title><link>http://kenji.blog/pt/tags/oauth2.0/</link><description>Recent content in OAuth2.0 on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>pt</language><copyright>kenjinote</copyright><lastBuildDate>Sun, 13 Sep 2026 12:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/pt/tags/oauth2.0/index.xml" rel="self" type="application/rss+xml"/><item><title>Como funciona o OAuth 2.0: Um guia ilustrado de implementação de uma integração com o Slack App</title><link>http://kenji.blog/pt/p/oauth2-architecture-slack-app-integration/</link><pubDate>Sun, 13 Sep 2026 12:00:00 +0900</pubDate><guid>http://kenji.blog/pt/p/oauth2-architecture-slack-app-integration/</guid><description>&lt;img src="http://kenji.blog/p/oauth2-architecture-slack-app-integration/img/eyecatch.jpg" alt="Featured image of post Como funciona o OAuth 2.0: Um guia ilustrado de implementação de uma integração com o Slack App" />&lt;h1 id="introdução-por-que-aprender-o-oauth-20">Introdução: Por que aprender o OAuth 2.0?
&lt;/h1>&lt;p>Nas aplicações web modernas, tornou-se comum que vários serviços funcionem em conjunto. Por exemplo, funcionalidades como &amp;ldquo;Fazer login com a conta do Google&amp;rdquo;, &amp;ldquo;Enviar uma notificação no Slack quando uma tarefa no Trello for atualizada&amp;rdquo; ou &amp;ldquo;Adicionar automaticamente um link de reunião do Zoom ao Google Calendar&amp;rdquo;. Nos bastidores de tudo isso atua o framework de autorização &lt;strong>OAuth 2.0 (Open Authorization 2.0)&lt;/strong>.&lt;/p>
&lt;p>No passado, quando os dados eram trocados entre diferentes serviços, métodos muito perigosos como &amp;ldquo;Autenticação Básica&amp;rdquo; e &amp;ldquo;Compartilhamento de Senha&amp;rdquo; eram usados, onde os usuários passavam seu ID e senha diretamente para o serviço conectado. No entanto, com esse método, o serviço parceiro assume controle total sobre as credenciais do usuário, o que envolve riscos fatais de segurança.&lt;/p>
&lt;p>O OAuth 2.0 foi criado como um protocolo padrão (RFC 6749) para delegar &amp;ldquo;apenas privilégios específicos (escopos)&amp;rdquo; por um &amp;ldquo;tempo limitado&amp;rdquo; a uma aplicação de terceiros, evitando esse &amp;ldquo;compartilhamento de senha&amp;rdquo;.&lt;/p>
&lt;p>Neste artigo, explicaremos o mecanismo deste OAuth 2.0 de forma extremamente detalhada e prática, através da implementação de uma aplicação (&lt;strong>Slack App&lt;/strong>) para o &lt;strong>Slack (Slack API)&lt;/strong>, que se tornou um padrão de facto para ferramentas de comunicação corporativa. É um guia definitivo de mais de 10.000 caracteres, cobrindo exemplos de código usando Node.js (Express), diagramas de sequência ilustrando o fluxo do protocolo e até mesmo o contexto matemático e criptográfico do parâmetro &lt;code>state&lt;/code> e do PKCE, que são conceitos essenciais para segurança.&lt;/p>
&lt;hr>
&lt;h1 id="1-conceitos-básicos-do-oauth-20-os-4-papéis-roles">1. Conceitos Básicos do OAuth 2.0: Os 4 Papéis (Roles)
&lt;/h1>&lt;p>O primeiro passo para entender o OAuth 2.0 é identificar com precisão os personagens (Roles). A RFC 6749 define os seguintes 4 papéis:&lt;/p>
&lt;pre class="mermaid">
graph TD
RO[&amp;#34;Resource Owner (Usuário)&amp;#34;] -- Concede autorização --&amp;gt; C[&amp;#34;Client (Slack App)&amp;#34;]
C -- Solicita autorização --&amp;gt; AS[&amp;#34;Authorization Server (Servidor de Autorização do Slack)&amp;#34;]
AS -- Emite token de acesso --&amp;gt; C
C -- Acessa usando o token --&amp;gt; RS[&amp;#34;Resource Server (Servidor da API do Slack)&amp;#34;]
RO -- Login com ID/senha --&amp;gt; AS
&lt;/pre>
&lt;ol>
&lt;li>&lt;strong>Resource Owner (Proprietário do Recurso)&lt;/strong>
&lt;ul>
&lt;li>A entidade capaz de conceder acesso a um recurso protegido. Geralmente refere-se ao &amp;ldquo;usuário final (humano)&amp;rdquo;. Neste exemplo, é &amp;ldquo;você mesmo, que pertence a um workspace do Slack e tem a permissão para postar mensagens em canais&amp;rdquo;.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Client (Cliente)&lt;/strong>
&lt;ul>
&lt;li>A aplicação que solicita acesso ao servidor de recursos com a permissão do proprietário do recurso. Neste exemplo, é &amp;ldquo;a aplicação Node.js (Slack App) que você está desenvolvendo&amp;rdquo;. Apesar do nome &amp;ldquo;cliente&amp;rdquo;, mesmo aplicações web executadas no lado do servidor são chamadas de &amp;ldquo;clientes&amp;rdquo; no contexto do OAuth.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Authorization Server (Servidor de Autorização)&lt;/strong>
&lt;ul>
&lt;li>O servidor que autentica o proprietário do recurso, obtém sua autorização e emite tokens de acesso para o cliente. Neste exemplo, é a infraestrutura de autenticação do Slack que fornece &lt;code>slack.com/oauth/v2/authorize&lt;/code>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Resource Server (Servidor de Recursos)&lt;/strong>
&lt;ul>
&lt;li>O servidor que hospeda os recursos protegidos e que recebe e responde a solicitações de acesso aos recursos utilizando tokens de acesso. Neste exemplo, são os endpoints em &lt;code>slack.com/api/&lt;/code> que fornecem APIs como &lt;code>chat.postMessage&lt;/code>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>Em poucas palavras, o fluxo do OAuth é &lt;strong>&amp;ldquo;uma série de etapas onde o Client, com o consentimento do Resource Owner, recebe um token de acesso do Authorization Server e o utiliza para acessar ou manipular dados do Resource Server&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;hr>
&lt;h1 id="2-dissecando-completamente-o-fluxo-de-concessão-de-código-de-autorização-authorization-code-grant">2. Dissecando Completamente o Fluxo de Concessão de Código de Autorização (Authorization Code Grant)
&lt;/h1>&lt;p>Existem vários fluxos (tipos de concessão) no OAuth 2.0, mas o mais recomendado e amplamente utilizado em ambientes onde o segredo do cliente (Client Secret) pode ser mantido seguro, como aplicações web executadas no lado do servidor, é o &lt;strong>Fluxo de Concessão de Código de Autorização (Authorization Code Grant)&lt;/strong>.&lt;/p>
&lt;p>A maior característica desse fluxo é a separação clara entre o &lt;strong>canal frontal (comunicação através do navegador)&lt;/strong> e o &lt;strong>canal traseiro (comunicação direta entre servidores)&lt;/strong>. Pelo canal frontal, apenas um &amp;ldquo;Código de Autorização (Authorization Code)&amp;rdquo; temporário é passado, enquanto a obtenção do &amp;ldquo;Token de Acesso&amp;rdquo; final é feita no canal traseiro, o que reduz drasticamente o risco de vazamento do token no histórico ou referrer do navegador.&lt;/p>
&lt;p>O diagrama de sequência abaixo mostra todas as etapas da concessão de código de autorização em um Slack App.&lt;/p>
&lt;pre class="mermaid">
sequenceDiagram
autonumber
participant U as &amp;#34;Resource Owner (Navegador Web)&amp;#34;
participant C as &amp;#34;Client (App Node.js)&amp;#34;
participant AS as &amp;#34;Authorization Server (Slack Auth)&amp;#34;
participant RS as &amp;#34;Resource Server (Slack API)&amp;#34;
U-&amp;gt;&amp;gt;C: &amp;#34;Clica no botão de instalar app (GET /slack/install)&amp;#34;
Note over C: &amp;#34;Geração do parâmetro state&amp;#34;
C--&amp;gt;&amp;gt;U: &amp;#34;Redirecionamento: 302 Found (Location: URL do Slack Auth)&amp;#34;
U-&amp;gt;&amp;gt;AS: &amp;#34;GET /oauth/v2/authorize?client_id=...&amp;amp;scope=...&amp;amp;state=...&amp;#34;
AS--&amp;gt;&amp;gt;U: &amp;#34;Tela de login do Slack &amp;amp; Tela de consentimento (Consent Screen)&amp;#34;
U-&amp;gt;&amp;gt;AS: &amp;#34;Concede permissões (Allow)&amp;#34;
Note over AS: &amp;#34;Geração do código de autorização (code)&amp;#34;
AS--&amp;gt;&amp;gt;U: &amp;#34;Redirecionamento: 302 Found (Location: URL de Callback do Client?code=...&amp;amp;state=...)&amp;#34;
U-&amp;gt;&amp;gt;C: &amp;#34;GET /slack/oauth_redirect?code=...&amp;amp;state=...&amp;#34;
Note over C: &amp;#34;Validação do parâmetro state (Prevenção de CSRF)&amp;#34;
C-&amp;gt;&amp;gt;AS: &amp;#34;POST /api/oauth.v2.access (code, client_id, client_secret)&amp;#34;
Note over C,AS: &amp;#34;Comunicação no canal traseiro (Não passa pelo navegador)&amp;#34;
AS--&amp;gt;&amp;gt;C: &amp;#34;200 OK (JSON: emissão de access_token etc.)&amp;#34;
C-&amp;gt;&amp;gt;RS: &amp;#34;POST /api/chat.postMessage (Authorization: Bearer &amp;lt;access_token&amp;gt;)&amp;#34;
RS--&amp;gt;&amp;gt;C: &amp;#34;200 OK (Sucesso ao postar mensagem)&amp;#34;
&lt;/pre>
&lt;p>Vamos detalhar esse fluxo passo a passo através de uma implementação concreta de código em Node.js (Express).&lt;/p>
&lt;hr>
&lt;h1 id="3-preparação-para-a-implementação-configurações-no-slack-developer-console">3. Preparação para a Implementação: Configurações no Slack Developer Console
&lt;/h1>&lt;p>Antes de escrever o código, precisamos registrar a existência de um &amp;ldquo;novo cliente&amp;rdquo; no sistema do Slack.&lt;/p>
&lt;ol>
&lt;li>Acesse &lt;a class="link" href="https://api.slack.com/apps" target="_blank" rel="noopener"
>Slack API: Applications&lt;/a> e clique em &amp;ldquo;Create New App&amp;rdquo;.&lt;/li>
&lt;li>Selecione &amp;ldquo;From scratch&amp;rdquo; e especifique o nome do aplicativo (ex: &lt;code>My First OAuth App&lt;/code>) e o workspace onde será instalado.&lt;/li>
&lt;li>Na tela &amp;ldquo;Basic Information&amp;rdquo; após a criação, obtenha as seguintes duas credenciais (credentials) importantes:
&lt;ul>
&lt;li>&lt;strong>Client ID&lt;/strong>: O ID que identifica unicamente e de forma pública o seu aplicativo. Não há problema em incluí-lo em requisições que passam pelo navegador (canal frontal).&lt;/li>
&lt;li>&lt;strong>Client Secret&lt;/strong>: A string secreta conhecida apenas pelo seu aplicativo. &lt;strong>Nunca o exponha no lado do navegador, e não deve ser comitado em plataformas como o GitHub.&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Vá para a tela &amp;ldquo;OAuth &amp;amp; Permissions&amp;rdquo; e registre a URL de callback em &amp;ldquo;Redirect URLs&amp;rdquo;. Assumindo o desenvolvimento local, definiremos o seguinte:
&lt;ul>
&lt;li>&lt;code>http://localhost:3000/slack/oauth_redirect&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>A preparação está concluída. Vamos para a implementação do servidor.&lt;/p>
&lt;hr>
&lt;h1 id="4-etapa-de-implementação-1-slackinstall-e-o-parâmetro-state-para-prevenção-de-csrf">4. Etapa de Implementação 1: &lt;code>/slack/install&lt;/code> e o parâmetro &lt;code>state&lt;/code> para prevenção de CSRF
&lt;/h1>&lt;p>Criaremos o primeiro endpoint para que os usuários comecem a utilizar o aplicativo (instalar no workspace). A principal responsabilidade aqui é redirecionar o usuário para o servidor de autorização do Slack, mas em termos de segurança, a &lt;strong>geração e armazenamento do parâmetro &lt;code>state&lt;/code>&lt;/strong> é extremamente crucial.&lt;/p>
&lt;h2 id="a-necessidade-do-parâmetro-state-prevenção-de-ataques-csrf">A Necessidade do Parâmetro state (Prevenção de Ataques CSRF)
&lt;/h2>&lt;p>Se o parâmetro &lt;code>state&lt;/code> não existisse, um atacante mal-intencionado poderia iniciar o processo de autorização com sua própria conta do Slack e fazer a vítima acessar uma URL de callback contendo o &amp;ldquo;código de autorização&amp;rdquo; obtido (ex: &lt;code>http://localhost:3000/slack/oauth_redirect?code=ATTACKER_CODE&lt;/code>). Quando o navegador da vítima o executa, a sessão da vítima é vinculada à conta do Slack do atacante, causando vazamento de informações e operações indesejadas (Login CSRF).&lt;/p>
&lt;p>Para prevenir isso, utiliza-se o &lt;code>state&lt;/code>, uma string aleatória imprevisível usada para verificar se o navegador que iniciou a requisição e o navegador que recebeu o callback são o mesmo.&lt;/p>
&lt;h2 id="a-entropia-do-state-contexto-matemático">A Entropia do state (Contexto Matemático)
&lt;/h2>&lt;p>Para gerar um &lt;code>state&lt;/code> seguro, precisamos de um número aleatório com &amp;ldquo;entropia&amp;rdquo; (quantidade de informação) suficiente. A entropia $E$ depende do número de possíveis strings $N$ que podem ser geradas, sendo representada pela fórmula a seguir:&lt;/p>
$$
E = \log_2(N) \quad (\text{unidade: bits})
$$&lt;p>Por exemplo, se gerarmos um número pseudo-aleatório criptograficamente seguro (CSPRNG) de 16 bytes e o convertermos para uma string hexadecimal (Hex), o número de estados possíveis será $2^{128}$.&lt;/p>
$$
E = \log_2(2^{128}) = 128 \text{ bits}
$$&lt;p>Com 128 bits de entropia, é na prática impossível (probabilidade astronômica) que um ataque de força bruta encontre uma colisão na ciência da computação moderna. Geralmente, os requisitos de segurança recomendam um &lt;code>state&lt;/code> com pelo menos 128 bits de entropia.&lt;/p>
&lt;h2 id="implementação-com-nodejs">Implementação com Node.js
&lt;/h2>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// app.js (trecho extraído)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">express&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;express&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">crypto&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;crypto&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">session&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;express-session&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">dotenv&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;dotenv&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">dotenv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">config&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">express&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Configuração do middleware de sessão (para salvar o state)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">use&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">session&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">secret&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SESSION_SECRET&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resave&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">saveUninitialized&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cookie&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">secure&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">false&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="c1">// No ambiente de produção deve ser true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">SLACK_CLIENT_ID&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SLACK_CLIENT_ID&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">SLACK_AUTHORIZE_URL&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://slack.com/oauth/v2/authorize&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/slack/install&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Gera um número aleatório forte de 16 bytes e converte para string hexadecimal (Entropia: 128 bits)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">state&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">crypto&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">randomBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">16&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;hex&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Salva na sessão para que possa ser verificado no momento do callback
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">session&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">oauth_state&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">state&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Lista de escopos (permissões) solicitadas (separados por vírgula)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// chat:write = Permissão para enviar mensagens no canal
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// channels:read = Permissão para obter informações de canais públicos
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">scope&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;chat:write,channels:read&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Parâmetros de URL para construir a URL do servidor de autorização do Slack
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">URLSearchParams&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">client_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">SLACK_CLIENT_ID&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">scope&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">scope&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">state&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">state&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">redirect_uri&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;http://localhost:3000/slack/oauth_redirect&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">authUrl&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">SLACK_AUTHORIZE_URL&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">?&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">params&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Redireciona o usuário para a tela de autorização do Slack (302 Found)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">redirect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">authUrl&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Ao acessar este endpoint, a resposta HTTP será algo como:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-http" data-lang="http">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">HTTP&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="m">1.1&lt;/span> &lt;span class="m">302&lt;/span> &lt;span class="ne">Found&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Location&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">https://slack.com/oauth/v2/authorize?client_id=123.456&amp;amp;scope=chat%3Awrite%2Cchannels%3Aread&amp;amp;state=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6&amp;amp;redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fslack%2Foauth_redirect&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Set-Cookie&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">connect.sid=...; Path=/; HttpOnly&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>O navegador do usuário navega instantaneamente para a &lt;code>Location&lt;/code> especificada, onde a tela do Slack (Consent Screen) é mostrada, exibindo a familiar tela &amp;ldquo;My First OAuth App está solicitando acesso ao workspace&amp;rdquo;.&lt;/p>
&lt;hr>
&lt;h1 id="5-etapa-de-implementação-2-recebendo-o-callback-e-trocando-o-token-de-acesso">5. Etapa de Implementação 2: Recebendo o Callback e Trocando o Token de Acesso
&lt;/h1>&lt;p>Quando o usuário clica em &amp;ldquo;Permitir (Allow)&amp;rdquo; na tela do Slack, os servidores do Slack redirecionam o navegador do usuário para o &lt;code>redirect_uri&lt;/code> que você configurou. Nesse momento, o &lt;code>code&lt;/code> (código de autorização) e o &lt;code>state&lt;/code> que você enviou anteriormente serão adicionados como parâmetros de query na URL.&lt;/p>
&lt;p>No backend, efetuamos o seguinte processo:&lt;/p>
&lt;ol>
&lt;li>Verificar se o &lt;code>state&lt;/code> recebido corresponde perfeitamente ao &lt;code>state&lt;/code> que foi salvo na sessão.&lt;/li>
&lt;li>Em caso positivo, enviar o &lt;code>code&lt;/code> recebido, seu próprio &lt;code>client_id&lt;/code> e o segredo &lt;code>client_secret&lt;/code> ao Slack API por meio da comunicação de canal traseiro (back-channel) para solicitar um token de acesso.&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">axios&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;axios&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">SLACK_CLIENT_SECRET&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SLACK_CLIENT_SECRET&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">SLACK_ACCESS_TOKEN_URL&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://slack.com/api/oauth.v2.access&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/slack/oauth_redirect&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">code&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">state&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">error&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">query&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Lidar com casos onde o usuário negou a autorização
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;access_denied&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">403&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Acesso negado.&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 1. Verificação do state (Prevenção de CSRF)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">savedState&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">session&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">oauth_state&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">state&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">state&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="nx">savedState&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">400&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Parâmetro State Inválido (Ataque CSRF Detectado)&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// O state usado deve ser excluído (Para prevenir ataques de repetição)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">delete&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">session&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">oauth_state&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 2. Trocar o código de autorização por um token de acesso (Comunicação de canal traseiro)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">tokenResponse&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">axios&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">SLACK_ACCESS_TOKEN_URL&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">URLSearchParams&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">client_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">SLACK_CLIENT_ID&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">client_secret&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">SLACK_CLIENT_SECRET&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">code&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">code&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">redirect_uri&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;http://localhost:3000/slack/oauth_redirect&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}).&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;application/x-www-form-urlencoded&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">tokenResponse&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ok&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Erro na Troca de Token:&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">500&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`Erro da API do Slack: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Sucesso! Obteve o token de acesso
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">accessToken&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">access_token&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">teamName&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">team&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">botUserId&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">bot_user_id&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`Instalado com sucesso no &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">teamName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">. Token de Acesso: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">accessToken&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Idealmente, é aqui que você salva o token no banco de dados após a criptografia
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// saveToDatabase(data.team.id, encrypt(accessToken));
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`A instalação está concluída! Workspace: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">teamName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Erro de Rede:&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">500&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Ocorreu um erro de comunicação.&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Em resposta a esta requisição a &lt;code>/api/oauth.v2.access&lt;/code>, o Slack retornará o seguinte JSON:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;ok&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;app_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;A12345678&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;authed_user&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;U12345678&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;scope&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;chat:write,channels:read&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;token_type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;bot&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;access_token&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;YOUR_BOT_TOKEN_HERE&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;bot_user_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;B12345678&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;team&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;T12345678&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;My Workspace&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;enterprise&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Essa string que começa com &lt;code>xoxb-&lt;/code> é o &lt;strong>Bot Access Token&lt;/strong> do Slack. Daqui em diante, sempre que a aplicação enviar uma requisição à API do Slack (Resource Server), você o fará adicionando &lt;code>Authorization: Bearer xoxb-...&lt;/code> nos cabeçalhos HTTP, o que provará a autenticação e autorização.&lt;/p>
&lt;hr>
&lt;h1 id="6-escopo-de-token-e-princípio-do-menor-privilégio-principle-of-least-privilege">6. Escopo de Token e Princípio do Menor Privilégio (Principle of Least Privilege)
&lt;/h1>&lt;p>Um dos conceitos mais cruciais no OAuth 2.0 é o &amp;ldquo;Escopo (Scope)&amp;rdquo;. Escopo refere-se ao limite de permissões associado a um token de acesso.&lt;/p>
&lt;p>No Slack, as permissões são categorizadas muito finamente e são amplamente divididas em &lt;strong>Bot Token Scopes&lt;/strong> e &lt;strong>User Token Scopes&lt;/strong>.&lt;/p>
&lt;ul>
&lt;li>&lt;code>chat:write&lt;/code> (Bot): Permissão para o próprio app (bot) postar mensagens no canal.&lt;/li>
&lt;li>&lt;code>chat:write&lt;/code> (User): Permissão para postar mensagens em nome do usuário que instalou o aplicativo (usando o nome e ícone do usuário).&lt;/li>
&lt;li>&lt;code>channels:read&lt;/code>: Permissão para obter a lista de canais.&lt;/li>
&lt;li>&lt;code>channels:history&lt;/code>: Permissão para ler o histórico de mensagens passadas do canal.&lt;/li>
&lt;/ul>
&lt;p>Seguindo o princípio de segurança definitivo de &amp;ldquo;Princípio do Menor Privilégio&amp;rdquo;, a regra de ouro é &lt;strong>solicitar apenas os escopos estritamente necessários para os recursos fornecidos pela aplicação&lt;/strong>. Por exemplo, se for uma aplicação &amp;ldquo;que apenas envia notificações&amp;rdquo;, ela deve requerer apenas &lt;code>chat:write&lt;/code> e nunca &lt;code>channels:history&lt;/code> (permissão para ler todo o histórico de conversas passadas). Isso é para minimizar os danos, caso a aplicação seja hackeada e ocorra o vazamento do token.&lt;/p>
&lt;hr>
&lt;h1 id="7-segurança-mais-avançada-pkce-proof-key-for-code-exchange">7. Segurança Mais Avançada: PKCE (Proof Key for Code Exchange)
&lt;/h1>&lt;p>Recentemente, o &lt;strong>PKCE (Proof Key for Code Exchange, RFC 7636, pronunciado como &amp;ldquo;pixy&amp;rdquo;)&lt;/strong> tem sido padronizado e é amplamente utilizado para aprimorar ainda mais a segurança do OAuth 2.0.&lt;/p>
&lt;p>Originalmente, o PKCE foi criado para &amp;ldquo;clientes públicos&amp;rdquo;, que não podem armazenar de forma segura o &lt;code>client_secret&lt;/code>, como aplicativos nativos (iOS/Android) ou SPAs (Single Page Applications). No entanto, hoje, as melhores práticas de segurança (Rascunho do OAuth 2.1) recomendam fortemente o uso de PKCE, mesmo em &amp;ldquo;clientes confidenciais&amp;rdquo; do lado do servidor.&lt;/p>
&lt;h2 id="como-o-pkce-funciona-e-o-seu-contexto-matemático">Como o PKCE funciona e o seu Contexto Matemático
&lt;/h2>&lt;p>O PKCE prova criptograficamente que a &amp;ldquo;entidade que iniciou a requisição de autorização&amp;rdquo; e a &amp;ldquo;entidade que está solicitando a troca do token&amp;rdquo; são a mesma pessoa.&lt;/p>
&lt;ol>
&lt;li>O cliente gera uma string aleatória &lt;strong>&lt;code>code_verifier&lt;/code>&lt;/strong> (de 43 a 128 caracteres).&lt;/li>
&lt;li>O cliente converte a mesma usando &lt;strong>SHA-256&lt;/strong>, e a codifica usando BASE64URL para obter o &lt;strong>&lt;code>code_challenge&lt;/code>&lt;/strong>.&lt;/li>
&lt;/ol>
&lt;p>Isso pode ser representado com a seguinte fórmula:&lt;/p>
$$
\text{code\_challenge} = \text{BASE64URL-ENCODE}( \text{SHA256}( \text{ASCII}(\text{code\_verifier}) ) )
$$&lt;ol start="3">
&lt;li>Ao executar &lt;code>/slack/install&lt;/code>, o cliente envia &lt;code>code_challenge&lt;/code> e &lt;code>code_challenge_method=S256&lt;/code> além do &lt;code>state&lt;/code> para o servidor de autorização (Slack), e o Slack armazena esse challenge temporariamente.&lt;/li>
&lt;li>Após o callback, durante a troca de tokens (&lt;code>/api/oauth.v2.access&lt;/code>), o cliente envia a versão pré-hash original, o &lt;strong>&lt;code>code_verifier&lt;/code>&lt;/strong>.&lt;/li>
&lt;li>O servidor de autorização (Slack) faz o hash SHA-256 do &lt;code>code_verifier&lt;/code> recebido para verificar se corresponde perfeitamente ao &lt;code>code_challenge&lt;/code> salvo no passo 3.&lt;/li>
&lt;/ol>
&lt;pre class="mermaid">
sequenceDiagram
participant C as &amp;#34;Client&amp;#34;
participant AS as &amp;#34;Authorization Server&amp;#34;
Note over C: &amp;#34;code_verifier = string aleatória&amp;#34;&amp;lt;br/&amp;gt;&amp;#34;code_challenge = SHA256(code_verifier)&amp;#34;
C-&amp;gt;&amp;gt;AS: &amp;#34;Requisição de autorização (Envia code_challenge)&amp;#34;
Note over AS: &amp;#34;Retém o code_challenge&amp;#34;
AS--&amp;gt;&amp;gt;C: &amp;#34;Emissão do código de autorização (code)&amp;#34;
C-&amp;gt;&amp;gt;AS: &amp;#34;Requisição do token (Envia code + code_verifier)&amp;#34;
Note over AS: &amp;#34;SHA256(verifier recebido) == challenge retido?&amp;#34;
AS--&amp;gt;&amp;gt;C: &amp;#34;Verificação bem-sucedida: emissão do token de acesso&amp;#34;
&lt;/pre>
&lt;p>Por esse mecanismo, mesmo que o &amp;ldquo;código de autorização (code)&amp;rdquo; seja roubado devido a um aplicativo malicioso ou intercepção no caminho de comunicação, o atacante não pode adquirir o token de acesso porque não sabe o &lt;code>code_verifier&lt;/code> original (devido à propriedade de via única do algoritmo de hash SHA-256, que torna impossível fazer a engenharia reversa do desafio para obter o verificador).&lt;/p>
&lt;p>Atualmente, o suporte ao PKCE está avançando em fluxos mais novos na API do Slack e em outras APIs SaaS modernas (Auth0, Okta, X/Twitter API v2, etc.), tornando-se uma tecnologia que desenvolvedores devem adotar ativamente.&lt;/p>
&lt;hr>
&lt;h1 id="8-gestão-segura-e-operação-de-tokens-de-acesso">8. Gestão Segura e Operação de Tokens de Acesso
&lt;/h1>&lt;p>Finalmente, uma breve discussão sobre as melhores práticas para guardar tokens de acesso obtidos de forma segura.&lt;/p>
&lt;h2 id="1-o-armazenamento-em-banco-de-dados-deve-ser-criptografado">1. O armazenamento em banco de dados deve ser criptografado
&lt;/h2>&lt;p>Um token de acesso (&lt;code>xoxb-...&lt;/code>) é basicamente a &amp;ldquo;chave mestra&amp;rdquo; do workspace do Slack. Você nunca deve guardá-lo como texto simples num banco de dados (MySQL, PostgreSQL, MongoDB, etc.). Na eventualidade improvável de que o banco de dados seja exposto em um ataque de Injeção de SQL, resultaria em um desastre massivo em que o Slack de todos os clientes seria tomado.&lt;/p>
&lt;p>Você sempre deve criptografá-lo primeiro na camada de aplicação usando criptografia simétrica forte como &lt;strong>AES-256-GCM&lt;/strong> antes de salvar no DB. A chave mestre para criptografia/descriptografia será gerenciada de forma estrita usando serviços seguros de gerenciamento de chaves, como AWS KMS (Key Management Service) ou GCP Cloud KMS.&lt;/p>
&lt;h2 id="2-rotação-de-tokens-token-rotation">2. Rotação de Tokens (Token Rotation)
&lt;/h2>&lt;p>Continuar usando tokens com uma validade longa de vida carrega riscos. As implementações mais recentes do OAuth recomendam a adoção de um mecanismo (Token Rotation) para reemitir um novo token de acesso a cada poucas horas usando &amp;ldquo;Refresh Tokens&amp;rdquo;. É possível ativar a rotação de tokens nas definições opcionais da API do Slack também.&lt;/p>
&lt;hr>
&lt;h1 id="conclusão">Conclusão
&lt;/h1>&lt;p>Neste artigo, explicamos detalhadamente o fluxo de Concessão de Código de Autorização do OAuth 2.0 através da implementação de códigos em Node.js para a integração do Slack App.&lt;/p>
&lt;ol>
&lt;li>Estando ciente dos &lt;strong>4 Papéis (RO, Client, AS, RS)&lt;/strong>, toda a arquitetura de sistema fica muito clara.&lt;/li>
&lt;li>O &lt;strong>Fluxo de Concessão de Código de Autorização&lt;/strong> garante segurança através do uso inteligente das vias de comunicação entre o navegador e o servidor (canal frontal / canal traseiro).&lt;/li>
&lt;li>Entender as mecânicas criptográficas por trás de aspectos como a prevenção de CSRF providenciada pelo &lt;strong>parâmetro &lt;code>state&lt;/code>&lt;/strong> ou a prevenção do ataque de intercepção do código de autorização pelo &lt;strong>PKCE&lt;/strong>, é um atalho vitalício na hora de escrever código seguro.&lt;/li>
&lt;li>Definir escopos baseados no &lt;strong>Princípio do Menor Privilégio&lt;/strong> e encriptar tokens no momento da guarda no banco de dados, são componentes indispensáveis que você deve dominar em uma operação.&lt;/li>
&lt;/ol>
&lt;p>OAuth 2.0 é extremamente profundo, com um volume massivo de especificações apenas dentro da RFC. Porém, ao testar, mexer no código na prática enquanto toma de alvo uma plataforma real (como o Slack), você consegue ver toda aquela ideologia refinada do design e aquele mecanismo de segurança robusto que ganha vida e entra no lugar. Espero que as informações partilhadas neste artigo sejam muito úteis no futuro em implementações de integração de API e desenvolvimento das suas aplicações.&lt;/p></description></item></channel></rss>