O Microsoft Graph é a porta de entrada para os serviços da Microsoft 365. Ele permite acessar informações como:
- Dados de usuários e grupos.
- Emails e calendários do Outlook.
- Arquivos no OneDrive.
- Mensagens no Teams.
Mas para chamar o Graph, não basta fazer um GET
com http.Client
.
É necessário autenticar via Microsoft Entra ID (antigo Azure Active Directory), obter um Access Token OAuth2, e então usá-lo em cada requisição.
👉 Isso pode complicar a aplicação principal, que precisa conhecer detalhes de autenticação.
Uma solução elegante é usar o padrão Sidecar: um serviço auxiliar, rodando ao lado da aplicação, que cuida dessa complexidade.
1. O que é um Sidecar?
Imagine que você tem uma aplicação (em Go, .NET, Python, etc) que precisa acessar o Graph.
Você pode embutir a lógica de autenticação dentro dela. Mas, se várias aplicações precisarem fazer isso, você terá código duplicado em todos os lugares.
O sidecar resolve esse problema:
- É um pequeno serviço separado (nesse caso em Go) que fica rodando ao lado da aplicação.
- Ele fala com o Microsoft Entra ID para buscar tokens.
- Ele chama o Microsoft Graph.
- Ele expõe uma API simples (via gRPC) para que a aplicação principal possa apenas pedir:
"Me dê os dados do usuário X"
E o sidecar cuida do resto.
📌 Em arquiteturas Kubernetes, o sidecar roda no mesmo pod da aplicação principal.
Isso significa que a aplicação acessa o sidecar por localhost
, sem depender da rede externa.
2. Arquitetura
📌 O que acontece:
- A aplicação chama
GetUser
no sidecar. - O sidecar verifica se já tem um Access Token válido em cache.
- Se não tiver, pede um novo ao Microsoft Entra ID.
- O sidecar chama o Microsoft Graph passando o token no header.
- O Graph devolve os dados do usuário.
- O sidecar converte para
UserResponse
e devolve para a aplicação via gRPC.
3. Definindo o contrato gRPC
Antes do código Go, precisamos definir o contrato de comunicação.
Crie o arquivo graph.proto
:
syntax = "proto3";
package graph;
// Serviço que o sidecar vai expor
service GraphService {
rpc GetUser (UserRequest) returns (UserResponse);
}
// Requisição: o cliente pode passar um user_id
// Se vazio, o sidecar consulta o "me" (usuário atual)
message UserRequest {
string user_id = 1;
}
// Resposta: dados básicos do usuário no Graph
message UserResponse {
string id = 1;
string display_name = 2;
string given_name = 3;
string surname = 4;
string user_principal_name = 5;
}
Explicando
- O serviço
GraphService
expõe o métodoGetUser
. -
UserRequest
permite consultar um usuário específico (users/{id}
) ou o próprio usuário (me
). -
UserResponse
retorna alguns campos comuns do Graph.
Gerando código Go
Com o protoc
instalado, rode:
protoc --go_out=. --go-grpc_out=. graph.proto
Isso gera os arquivos .pb.go
, que contêm as interfaces gRPC que implementaremos.
4. Implementando o sidecar em Go
4.1 Estrutura base
Crie um arquivo sidecar.go
:
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"os"
"sync"
"time"
pb "example.com/graph/proto" // ajuste para o caminho correto
"google.golang.org/grpc"
)
const (
tokenURL = "https://login.microsoftonline.com/%s/oauth2/v2.0/token"
graphURL = "https://graph.microsoft.com/v1.0/%s"
)
// Servidor gRPC que vai rodar como sidecar
type server struct {
pb.UnimplementedGraphServiceServer
mu sync.Mutex
accessToken string
expiration time.Time
}
📌 Aqui definimos:
- As constantes com os endpoints de autenticação (Entra ID) e do Graph.
-
Uma struct
server
que:- Implementa nosso serviço gRPC.
- Guarda em memória (
accessToken
,expiration
) o último token obtido.
4.2 Gerenciamento de Token
func (s *server) getToken(ctx context.Context) (string, error) {
s.mu.Lock()
defer s.mu.Unlock()
// Se já temos token válido, reutiliza
if time.Now().Before(s.expiration) && s.accessToken != "" {
return s.accessToken, nil
}
// Senão, pede um novo token ao Entra ID
tenantID := os.Getenv("ENTRA_TENANT_ID")
clientID := os.Getenv("ENTRA_CLIENT_ID")
clientSecret := os.Getenv("ENTRA_CLIENT_SECRET")
url := fmt.Sprintf(tokenURL, tenantID)
data := []byte(fmt.Sprintf(
"client_id=%s&scope=https%%3A%%2F%%2Fgraph.microsoft.com%%2F.default&client_secret=%s&grant_type=client_credentials",
clientID, clientSecret,
))
req, _ := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(data))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("erro ao obter token: %s", string(body))
}
var token struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
}
if err := json.Unmarshal(body, &token); err != nil {
return "", err
}
// Atualiza cache (renova 1 min antes do vencimento)
s.accessToken = token.AccessToken
s.expiration = time.Now().Add(time.Duration(token.ExpiresIn-60) * time.Second)
return s.accessToken, nil
}
Explicando
- Cache de token: o sidecar não pede token toda hora → economiza requisições.
-
Mutex (
mu
): garante que múltiplas chamadas concorrentes não façamPOST
ao mesmo tempo. - Renovação antecipada: 1 minuto antes de expirar, o sidecar já pede um novo.
4.3 Implementando GetUser
func (s *server) GetUser(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) {
// 1. Garantir token válido
token, err := s.getToken(ctx)
if err != nil {
return nil, err
}
// 2. Montar endpoint
userEndpoint := "me"
if req.UserId != "" {
userEndpoint = "users/" + req.UserId
}
url := fmt.Sprintf(graphURL, userEndpoint)
httpReq, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
httpReq.Header.Set("Authorization", "Bearer "+token)
// 3. Fazer requisição ao Microsoft Graph
resp, err := http.DefaultClient.Do(httpReq)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("erro ao buscar usuário: %s", string(body))
}
// 4. Mapear resposta para nosso UserResponse
var user map[string]interface{}
if err := json.Unmarshal(body, &user); err != nil {
return nil, err
}
return &pb.UserResponse{
Id: user["id"].(string),
DisplayName: user["displayName"].(string),
GivenName: user["givenName"].(string),
Surname: user["surname"].(string),
UserPrincipalName: user["userPrincipalName"].(string),
}, nil
}
Explicando
- Pega o token em cache (ou renova).
- Define o endpoint (
/me
ou/users/{id}
). - Faz um
GET
no Microsoft Graph. - Converte a resposta JSON para a struct
UserResponse
.
4.4 Subindo o servidor gRPC
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
panic(err)
}
grpcServer := grpc.NewServer()
pb.RegisterGraphServiceServer(grpcServer, &server{})
fmt.Println("Sidecar Microsoft Graph rodando em :50051")
if err := grpcServer.Serve(lis); err != nil {
panic(err)
}
}
Explicando
- O sidecar ouve na porta
50051
. - Ele registra o serviço
GraphService
. - Ele fica rodando, pronto para responder às chamadas gRPC da aplicação principal.
5. Executando
- Configure variáveis de ambiente:
export ENTRA_CLIENT_ID="seu-client-id"
export ENTRA_CLIENT_SECRET="seu-client-secret"
export ENTRA_TENANT_ID="seu-tenant-id"
- Rode o sidecar:
go run sidecar.go
- Teste com
grpcurl
:
grpcurl -plaintext -d '{}' localhost:50051 graph.GraphService/GetUser
6. Exemplo de resposta
{
"id": "1234abcd-...",
"display_name": "João Silva",
"given_name": "João",
"surname": "Silva",
"user_principal_name": "joao@empresa.com"
}
7. Por que usar Sidecar?
- Simplicidade: a aplicação só chama gRPC, sem se preocupar com OAuth2.
- Reuso: múltiplos serviços podem compartilhar o mesmo sidecar.
- Segurança: credenciais ficam apenas no sidecar.
- Escalabilidade: sidecars podem ser replicados em pods diferentes.
- Observabilidade: métricas e logs podem ser centralizados no sidecar.
Conclusão
Criamos um sidecar em Go que:
- Autentica no Microsoft Entra ID via Client Credentials Flow.
- Usa o Microsoft Graph para consultar usuários.
- Exponde um serviço gRPC simples para a aplicação principal.
Com isso, conseguimos um design mais limpo, seguro e escalável, separando responsabilidades e aproveitando os benefícios do padrão Sidecar.
💡Curtiu?
Se quiser trocar ideia sobre IA, cloud e arquitetura, me segue nas redes:
Publico conteúdos técnicos direto do campo de batalha. E quando descubro uma ferramenta que economiza tempo e resolve bem, como essa, você fica sabendo também.
Top comments (0)