DEV Community

Cover image for Criando um Sidecar em Go para acessar o Microsoft Graph via gRPC
Cláudio Filipe Lima Rapôso
Cláudio Filipe Lima Rapôso

Posted on

Criando um Sidecar em Go para acessar o Microsoft Graph via gRPC

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

Diagrama de Sequência

📌 O que acontece:

  1. A aplicação chama GetUser no sidecar.
  2. O sidecar verifica se já tem um Access Token válido em cache.
  • Se não tiver, pede um novo ao Microsoft Entra ID.
    1. O sidecar chama o Microsoft Graph passando o token no header.
    2. O Graph devolve os dados do usuário.
    3. 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; } 
Enter fullscreen mode Exit fullscreen mode

Explicando

  • O serviço GraphService expõe o método GetUser.
  • 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 
Enter fullscreen mode Exit fullscreen mode

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 } 
Enter fullscreen mode Exit fullscreen mode

📌 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 } 
Enter fullscreen mode Exit fullscreen mode

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çam POST 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 } 
Enter fullscreen mode Exit fullscreen mode

Explicando

  1. Pega o token em cache (ou renova).
  2. Define o endpoint (/me ou /users/{id}).
  3. Faz um GET no Microsoft Graph.
  4. 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) } } 
Enter fullscreen mode Exit fullscreen mode

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

  1. 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" 
Enter fullscreen mode Exit fullscreen mode
  1. Rode o sidecar:
 go run sidecar.go 
Enter fullscreen mode Exit fullscreen mode
  1. Teste com grpcurl:
 grpcurl -plaintext -d '{}' localhost:50051 graph.GraphService/GetUser 
Enter fullscreen mode Exit fullscreen mode

6. Exemplo de resposta

{ "id": "1234abcd-...", "display_name": "João Silva", "given_name": "João", "surname": "Silva", "user_principal_name": "joao@empresa.com" } 
Enter fullscreen mode Exit fullscreen mode

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:

  1. Autentica no Microsoft Entra ID via Client Credentials Flow.
  2. Usa o Microsoft Graph para consultar usuários.
  3. 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)