
_Créditos das imagens: ChatGPT
# Autenticação OAuth2 em Harbour para Publicação no Blogger
**Publicado em:** 11 de fevereiro de 2025
## Introdução
No universo das aplicações web modernas, o protocolo OAuth2 se tornou o padrão para a autenticação e autorização de acesso a APIs de terceiros, como as do Google. Neste post, vamos explorar como usar Harbour — uma linguagem derivada do Clipper — para implementar um fluxo de autenticação OAuth2. O objetivo é automatizar a publicação de conteúdos em um blog no Blogger, integrando a obtenção de notícias, a conversão para Markdown/HTML e, finalmente, a postagem no blog.
## Visão Geral do Código
O exemplo de código Harbour realiza os seguintes passos:
1. **Obtenção de Notícias:** Utiliza a API de um serviço de notícias (através do TIPClientHTTP) para buscar conteúdos relacionados à tecnologia.
2. **Processamento do Conteúdo:** Converte o JSON retornado em um texto formatado em Markdown e, em seguida, converte esse Markdown para HTML — o formato exigido pelo Blogger.
3. **Fluxo OAuth2 para Autenticação:** Implementa o fluxo de autorização do OAuth2 para obter um token de acesso. Esse token é necessário para autorizar a requisição de publicação no Blogger.
4. **Publicação no Blogger:** Com o token em mãos, o código envia uma requisição POST à API do Blogger, publicando o conteúdo processado.
## Configuração do Ambiente e Bibliotecas
O código inicia com definições e inclusão de bibliotecas essenciais, como:
- **`hbssl` e `hbtip`:** Para lidar com conexões seguras e requisições HTTP.
- **Configuração de CodePage e otimizações:** As diretivas `#pragma` ajudam a manter o código otimizado e compatível com diferentes ambientes.
Além disso, variáveis estáticas são definidas para armazenar as credenciais e URLs necessárias, incluindo as chaves do serviço de notícias, ID do blog no Blogger e os parâmetros do OAuth2 (como `client_id`, `client_secret`, `redirect_uri`, entre outros).
## Fluxo de Autenticação OAuth2
### 1. Iniciando o Fluxo OAuth2
A função `GetOAuth2Token()` é o ponto de partida para a obtenção do token de acesso. Essa função realiza as seguintes ações:
- **Inicialização do Servidor HTTP Local:** Utiliza o UHttpd para iniciar um servidor na porta 8002, que ficará responsável por gerenciar as rotas necessárias para o fluxo OAuth2.
- **Abertura do Navegador:** A função `LaunchCommand()` é chamada para abrir o navegador (neste caso, o Microsoft Edge) e exibir uma página HTML que direciona o usuário para a autenticação.
### 2. Redirecionamento para Autenticação
A função `AuthHandler()` monta os parâmetros necessários para a requisição do OAuth2. Entre os parâmetros, destacam-se:
- **`redirect_uri`:** Onde o usuário será redirecionado após a autenticação.
- **`response_type`:** Geralmente definido como `"code"`, pois estamos utilizando o Authorization Code Flow.
- **`client_id` e `scope`:** Informações que identificam a aplicação e os recursos solicitados (neste exemplo, acesso à API do Blogger).
Após montar os parâmetros, a função redireciona o navegador para a URL de autorização do Google, onde o usuário poderá consentir com o acesso.
### 3. Recebendo o Código de Autorização
Quando o usuário autoriza o acesso, o Google redireciona o navegador para a rota definida (por exemplo, `/oauth2callback`). A função `CallbackHandler()` captura o código de autorização (`code`) enviado como parâmetro na URL.
### 4. Troca do Código pelo Token de Acesso
Com o código de autorização em mãos, a função `ExchangeCodeForToken()` realiza uma requisição POST para a URL de token do Google. Os parâmetros enviados incluem:
- **`code`:** O código de autorização recebido.
- **`client_id` e `client_secret`:** Credenciais da aplicação.
- **`redirect_uri`:** Confirmação da URL de redirecionamento.
- **`grant_type`:** Definido como `"authorization_code"`.
Se a requisição for bem-sucedida, o JSON de resposta conterá o `access_token`, que é então armazenado e utilizado para autorizar a publicação no Blogger.
## Publicando o Post no Blogger
Após obter o token de acesso, a função `PublishToBlogger()` monta uma requisição HTTP para a API do Blogger. Os passos incluem:
1. **Montagem da URL:** A URL é construída utilizando o ID do blog.
2. **Criação do Corpo da Requisição:** Um objeto JSON é formado contendo os dados do post, como título e conteúdo (já convertido para HTML).
3. **Configuração dos Cabeçalhos:** Inclui o cabeçalho de autorização com o token no formato `Bearer `.
4. **Envio da Requisição:** Se a requisição POST for bem-sucedida, o post é publicado no blog.
## Considerações Finais
Este exemplo demonstra como é possível integrar diversas tecnologias — requisições HTTP, manipulação de JSON, conversão de formatos e autenticação OAuth2 — em uma única aplicação desenvolvida em Harbour. A utilização do OAuth2 garante que as credenciais sejam tratadas de maneira segura, permitindo acesso controlado à API do Blogger.
Além de servir como base para automação de postagens, o código pode ser adaptado para outras aplicações que exigem acesso seguro a serviços de terceiros, ampliando o leque de possibilidades para desenvolvedores que trabalham com Harbour.
---
Esperamos que este tutorial tenha esclarecido como implementar o fluxo OAuth2 via Harbour para a publicação de posts no Blogger. Se você tiver dúvidas ou sugestões, deixe seu comentário abaixo!
---
Esta postagem foi feita para inspirar desenvolvedores que desejam explorar integrações modernas utilizando Harbour e protocolos de autenticação seguros. Acompanhe nosso blog para mais conteúdos sobre tecnologias, programação e inovação.
---
### Utilizei o exemplo abaixo para as últimas publicações do "BlackTDN News" no Blogger.
### Código: [hb_blogger_post.prg](https://github.com/naldodj/naldodj-hb_blogger_post/blob/main/src/hb_blogger_post.prg)
```
/* Keeping it tidy */
#pragma -w3
#pragma -es2
/* Optimizations */
#pragma -km+
#pragma -ko+
#require "hbssl"
#require "hbtip"
REQUEST HB_CODEPAGE_UTF8EX
#if ! defined( __HBSCRIPT__HBSHELL )
REQUEST __HBEXTERN__HBSSL__
#endif
#include "tip.ch"
#include "inkey.ch"
#include "fileio.ch"
#include "hbclass.ch"
// Defina suas credenciais e chaves (substitua pelos seus valores)
static cNewsApiKey as character // API key para o serviço de notícias
static cBloggerID as character // ID do seu blog no Blogger
static cBloggerAccessToken as character // Blogger API Key
// Variáveis globais para OAuth2
static cClientID as character
static cClientSecret as character
static cRedirectURI as character
static cAuthURL as character
static cTokenURL as character
static cScope as character
static cEOL as character
MEMVAR server, get, post, cookie, session
//---------------------------------------------------------------------
// Função: Main()
// Executa o fluxo: busca notícias -> gera Markdown -> converte para HTML -> publica no Blogger
//---------------------------------------------------------------------
procedure Main()
local cHtml as character
local cTitle as character
local cMarkdown as character
local cNewsJSON as character
local lSuccess as logical:=.F.
cEOL:=hb_eol()
hb_setCodePage("UTF8")
hb_cdpSelect("UTF8EX")
SET DATE ANSI
SET CENTURY ON
#ifdef __ALT_D__ // Compile with -b
AltD(1) // Enables the debugger. Press F5 to go.
AltD() // Invokes the debugger
#endif
begin sequence
cNewsApiKey:=GetEnv("NEWS_API_KEY") // API key para o serviço de notícias
// 1. Buscar notícias de tecnologia
cNewsJSON:=GetNews()
// 2. Processar JSON e gerar Markdown
cMarkdown:=JSONToMarkdown(cNewsJSON)
// 3. Converter Markdown para HTML (Blogger requer HTML)
if (!Empty(cMarkdown))
cHtml:=ConvertMarkdownToHtml(cMarkdown)
// 4. Publicar o post no Blogger
cTitle:="BlackTDN NEWS :: "+DToC(Date())+" :: "+Time()
lSuccess:=PublishToBlogger(cTitle,cHtml)
endif
if (lSuccess)
? "Post publicado com sucesso!"
else
? "Falha ao publicar o post."
endif
end sequence
return
//---------------------------------------------------------------------
// Função: GetNews()
// Usa TIPClientHTTP para efetuar uma requisição GET à API de notícias.
//---------------------------------------------------------------------
static function GetNews()
local cURL as character
local cResponse as character
local oURL as object
local oHTTP as object
cURL:="https://newsapi.org/v2/everything/"
//FixMe!
cURL:=strTran(cURL,"https","http")
// Cria objeto TUrl (construtor definido na TIP – ver tipwget.prg)
oURL:=TUrl():New(cURL)
oHTTP:=TIPClientHTTP():New(oURL)
oHTTP:hFields["Content-Type"]:="application/JSON"
oHTTP:hFields["X-Api-Key"]:=cNewsApiKey
/* build the search query and add it to the TUrl object */
oHTTP:oURL:addGetForm(;
{;
"q" => "tecnologia";
,"sortBy" => "popularity";
,"language" => "pt";
,"from" => hb_DToC(Date(),"yyyy-mm-dd");
,"to" => hb_DToC(Date(),"yyyy-mm-dd");
,"apiKey" => cNewsApiKey;
};
)
/* Connect to the HTTP server */
oHTTP:nConnTimeout:=2000 /* 20000 */
? "Connecting to",oURL:cProto+"://"+oURL:cServer+oURL:cPath+"?"+oURL:cQuery
if (oHTTP:Open())
? "Connection status:",iif(Empty(oHTTP:cReply),"",oHTTP:cReply)
/* download the response */
cResponse:=oHTTP:ReadAll()
if (Empty(cResponse))
? oHTTP:LastErrorMessage(oHTTP:SocketCon)
endif
oHTTP:Close()
else
? oHTTP:LastErrorMessage(oHTTP:SocketCon)
endif
hb_default(@cResponse,"")
return(cResponse) as character
//---------------------------------------------------------------------
// Função: JSONToMarkdown(cJSON)
// Gera um texto em Markdown formatado com título, descrição e link para cada notícia.
//---------------------------------------------------------------------
static function JSONToMarkdown(cJSON as character)
local aArticles as array
local cMarkdown as character := ""
local hJSON as hash
local hArticle as hash
local i as numeric
hb_MemoWrit("C:\tmp\news.json",cJSON)
hb_JSONDecode(cJSON,@hJSON)
begin sequence
if (!hb_HHasKey(hJSON,"status"))
break
endif
if (hJSON["status"]!="ok")
break
endif
if (hb_HHasKey(hJSON,"totalResults"))
if (hJSON["totalResults"]==0)
break
endif
endif
if (!hb_HHasKey(hJSON,"articles"))
break
endif
aArticles:=hJSON["articles"]
for i:=1 to Len(aArticles)
hArticle:=aArticles[i]
// Título da notícia
cMarkdown+="# "+if(!Empty(hArticle["title"]),hArticle["title"],hArticle["description"])+cEOL
// Fonte e autor
cMarkdown+="**Fonte:** "+hArticle["source"]["name"]+cEOL
if (!Empty(hArticle["author"]))
cMarkdown+="**Autor:** "+hArticle["author"]+cEOL
endif
// Data de publicação
cMarkdown+="**Publicado em:** "+hArticle["publishedAt"]+cEOL+cEOL
// Imagem (se disponível)
if (!Empty(hArticle["urlToImage"]))
cMarkdown+=""+cEOL+cEOL
endif
// Descrição (citação)
if (!Empty(hArticle["description"]))
cMarkdown+="> "+hArticle["description"]+cEOL+cEOL
endif
// Link para a notícia completa
cMarkdown+="[Leia mais]("+hArticle["url"]+")"+cEOL+cEOL
// Separador entre notícias
cMarkdown+="---"+cEOL+cEOL
next i
end sequence
return(cMarkdown) as character
//---------------------------------------------------------------------
// Função: ConvertMarkdownToHtml( cMarkdown )
//---------------------------------------------------------------------
static function ConvertMarkdownToHtml(cMarkdown as character)
local cNewMarkDown as character
#pragma __cstream|cNewMarkDown:=%s
#pragma __endtext
cNewMarkDown+=cEOL
cNewMarkDown+='_Créditos das imagens: ChatGPT'+cEOL+cMarkdown+cEOL+'
'
return(cNewMarkDown) as character
//---------------------------------------------------------------------
// Função: PublishToBlogger( cTitle, cContent )
// Publica o post no Blogger via API REST usando TIPClientHTTP.
//---------------------------------------------------------------------
static function PublishToBlogger(cTitle as character,cContent as character)
local cURL as character
local cAccessToken as character
local cJSONData as character
local cResponse as character
local hJSONData as hash
local oURL as object
local oHTTP as object
cBloggerID:=GetEnv("BLOGGER_ID") // ID do seu blog no Blogger
cBloggerAccessToken:=GetEnv("BLOGGER_API_KEY") // Blogger API Key
// Variáveis globais para OAuth2
cClientID:=GetEnv("GOOGLE_CLIENT_ID")
cClientSecret:=GetEnv("GOOGLE_CLIENT_SECRET")
cRedirectURI:="http://localhost:8002/oauth2callback"
cAuthURL:="https://accounts.google.com/o/oauth2/v2/auth"
cTokenURL:="https://accounts.google.com/o/oauth2/token"
cScope:="https://www.googleapis.com/auth/blogger"
cAccessToken:=GetOAuth2Token() // Obter token via OAuth2
if (!Empty(cAccessToken))
// Constrói a URL do endpoint de inserção de posts do Blogger
cURL:="https://www.googleapis.com/blogger/v3/blogs/"+cBloggerID+"/posts/"
// Prepara o corpo JSON da requisição conforme a documentação do Blogger API v3
hJSONData:={;
"kind" => "blogger#post";
,"blog" => {;
"id" => cBloggerID;
};
,"title" => cTitle;
,"content" => cContent;
}
// Cria o objeto TIPClientHTTP para a URL do Blogger
oURL:=TUrl():New(cURL)
oHTTP:=TIPClientHTTP():New(oURL)
// Define os cabeçalhos adicionais (em hFields – conforme a implementação TIP)
oHTTP:hFields["Authorization"]:="Bearer "+cAccessToken
oHTTP:hFields["Content-Type"]:="application/JSON"
// Efetua a requisição POST com o corpo JSON
cJSONData:=hb_JSONEncode(hJSONData)
if (oHTTP:Open())
if (oHTTP:Post(cJSONData))
? cResponse:=oHTTP:cReply
? hb_ValToExp(oHTTP:hHeaders)
else
? "Error:", "oHTTP:Post()", oHTTP:LastErrorMessage()
endif
oHTTP:Close()
else
? "Error:", "oHTTP:Open()", oHTTP:LastErrorMessage()
endif
endif
return(!Empty(cResponse)) as logical
//---------------------------------------------------------------------
// Função: GetOAuth2Token()
// Obtém um token de acesso via OAuth2 (Authorization Code Flow)
//---------------------------------------------------------------------
static function GetOAuth2Token()
local cAccessToken as character
local lHTTPServer as logical
local oLogError,oLogAccess as object
local oHTTPServer as object
? "http://localhost:8002/"
hb_idleSleep(.1)
oLogError:=UHttpdLog():New("hb_blogger_post_error.log")
oLogAccess:=UHttpdLog():New("hb_blogger_post_access.log")
if (hb_FileExists(".uhttpd.stop"))
fErase(".uhttpd.stop")
endif
// Configurar rotas
oHTTPServer:=UHttpdNew()
lHTTPServer:=oHTTPServer:Run(;
{;
"FirewallFilter" => "";
,"LogAccess" => {| m | oLogAccess:Add( m+cEOL ) };
,"LogError" => {| m | oLogError:Add( m+cEOL ) };
,"Trace" => {| ... | QOut( ... ) };
,"Port" => 8002;
,"Idle" => {|o|iif(hb_FileExists(".uhttpd.stop"),(fErase(".uhttpd.stop"),o:Stop()),NIL)};
,"SSL" => .F.;
,"Mount" => {;
"/info" => {||UProcInfo()};
,"/auth" => @AuthHandler();// Inicia o fluxo OAuth2
,"/oauth2callback" => @CallbackHandler();// Recebe o código de autorização
,"/" => {||URedirect("/auth")};
};
};
)
oLogError:Close()
oLogAccess:Close()
if (lHTTPServer)
if (file("hb_blogger_post.token"))
cAccessToken:=hb_MemoRead("hb_blogger_post.token")
fErase("hb_blogger_post.token")
else
hb_default(@cAccessToken,"")
endif
else
hb_default(@cAccessToken,"")
endif
return(cAccessToken)
static function AuthHandler()
local cParams as character:=""
local cKey,cValue as character
local hkey as hash
local hkeys as hash := {;
"redirect_uri" => cRedirectURI;
,"prompt" => "consent";
,"response_type" => "code";
,"client_id" => cClientID;
,"scope" => cScope;
}
for each hkey in hkeys
cKey:=hKey:__enumKey()
cValue:=hKey:__enumValue()
cParams+=tip_URLEncode(AllTrim(hb_CStr(cKey)))+"="+tip_URLEncode(AllTrim(cValue))
if (!hkey:__enumIsLast())
cParams+="&"
endif
next each
if (Right(cParams,1)=="&")
cParams:=SubStr(cParams,1,Len(cParams)-1)
endif
USessionStart()
URedirect(cAuthURL+"?"+cParams) // Redireciona o navegador
return(.T.)
static function CallbackHandler()
local cAuthCode,cAccessToken as character
if (hb_HHasKey(get,"code"))
cAuthCode:=get["code"]
cAccessToken:=ExchangeCodeForToken(cAuthCode)
hb_MemoWrit("hb_blogger_post.token",cAccessToken)
endif
hb_MemoWrit(".uhttpd.stop","")
return({"AccessToken"=>cAccessToken})
static function ExchangeCodeForToken(cAuthCode as character)
local cTokenResponse, cAccessToken
local hkeys,hJSONResponse as hash
local oURL as object
local oHTTP as object
// 2. Trocar código por token de acesso
hkeys:={;
"code" => cAuthCode,;
"redirect_uri" => cRedirectURI,;
"client_id" => cClientID,;
"client_secret" => cClientSecret,;
"grant_type" => "authorization_code";
}
// Cria o objeto TIPClientHTTP para a URL do Blogger
oURL:=TUrl():New(cTokenURL)
oHTTP:=TIPClientHTTP():New(oURL)
oHTTP:hFields["Content-Type"]:="application/x-www-form-urlencoded"
if (oHTTP:Open())
if (oHTTP:Post(hkeys))
cTokenResponse:=oHTTP:ReadAll()
QOut( "cTokenResponse", cTokenResponse )
hb_JSONDecode(cTokenResponse,@hJSONResponse)
if (hb_HHasKey(hJSONResponse,"access_token"))
cAccessToken:=hJSONResponse["access_token"]
QOut( "cAccessToken", cAccessToken )
hb_MemoWrit("hb_blogger_post.token",cAccessToken)
hb_MemoWrit(".uhttpd.stop","")
endif
? hb_ValToExp(oHTTP:hHeaders)
else
QOut( "Error:", "oHTTP:Post()", oHTTP:LastErrorMessage() )
? "Error:", "oHTTP:Post()", oHTTP:LastErrorMessage()
endif
oHTTP:Close()
else
? "Error:", "oHTTP:Open()", oHTTP:LastErrorMessage()
QOut( "Error:", "oHTTP:Open()", oHTTP:LastErrorMessage() )
endif
return(cAccessToken)
//---------------------------------------------------------------------
// Fim do programa
//---------------------------------------------------------------------
```
---
### Dependências: hbmk.hbm
```
-w3 -es2
xhb.hbc
hbct.hbc
hbtip.hbc
hbssl.hbc
hbhttpd.hbc
```
---
### HashTags:
#HarbourLang, #OAuth2, #APIBlogger, #Automação, #Programação, #Desenvolvimento, #AutenticaçãoSegura, #APIs, #JSON #CódigoAberto
Comentários
Postar um comentário