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
GetUserno 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
UserResponsee 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
GraphServiceexpõe o métodoGetUser. -
UserRequestpermite consultar um usuário específico (users/{id}) ou o próprio usuário (me). -
UserResponseretorna 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
serverque:- 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çamPOSTao 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 (
/meou/users/{id}). - Faz um
GETno 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)