Initial example authentication to Azure ad
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
setup.sh
|
||||||
|
.env
|
14
go.mod
Normal file
14
go.mod
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module example/hello
|
||||||
|
|
||||||
|
go 1.24.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/coreos/go-oidc v2.3.0+incompatible
|
||||||
|
golang.org/x/oauth2 v0.29.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/pquerna/cachecontrol v0.2.0 // indirect
|
||||||
|
golang.org/x/crypto v0.37.0 // indirect
|
||||||
|
gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect
|
||||||
|
)
|
22
go.sum
Normal file
22
go.sum
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
github.com/coreos/go-oidc v2.3.0+incompatible h1:+5vEsrgprdLjjQ9FzIKAzQz1wwPD+83hQRfUIPh7rO0=
|
||||||
|
github.com/coreos/go-oidc v2.3.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k=
|
||||||
|
github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
|
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||||
|
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
||||||
|
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs=
|
||||||
|
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
126
main.go
Normal file
126
main.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/microsoft"
|
||||||
|
"github.com/coreos/go-oidc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
clientID = os.Getenv("AZURE_CLIENT_ID")
|
||||||
|
clientSecret = os.Getenv("AZURE_CLIENT_SECRET")
|
||||||
|
redirectURL = os.Getenv("AZURE_REDIRECT_URL") // e.g., http://localhost:5000/callback
|
||||||
|
tenantID = os.Getenv("AZURE_TENANT_ID")
|
||||||
|
|
||||||
|
provider *oidc.Provider
|
||||||
|
verifier *oidc.IDTokenVerifier
|
||||||
|
oauth2Config *oauth2.Config
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
issuer := fmt.Sprintf("https://login.microsoftonline.com/%s/v2.0", tenantID)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
provider, err = oidc.NewProvider(ctx, issuer)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to get provider: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
verifier = provider.Verifier(&oidc.Config{ClientID: clientID})
|
||||||
|
|
||||||
|
oauth2Config = &oauth2.Config{
|
||||||
|
ClientID: clientID,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
Endpoint: microsoft.AzureADEndpoint(tenantID),
|
||||||
|
RedirectURL: redirectURL,
|
||||||
|
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||||
|
}
|
||||||
|
|
||||||
|
http.HandleFunc("/", handleIndex)
|
||||||
|
http.HandleFunc("/callback", handleCallback)
|
||||||
|
|
||||||
|
log.Println("Server started at http://localhost:5000")
|
||||||
|
log.Fatal(http.ListenAndServe(":5000", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
|
idTokenCookie, err := r.Cookie("id_token")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("No id_token cookie found:", err)
|
||||||
|
http.Redirect(w, r, oauth2Config.AuthCodeURL("state", oauth2.AccessTypeOffline), http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := verifier.Verify(r.Context(), idTokenCookie.Value)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Token verification failed:", err)
|
||||||
|
// Clear the invalid cookie to avoid another loop
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: "id_token",
|
||||||
|
Value: "",
|
||||||
|
Path: "/",
|
||||||
|
MaxAge: -1,
|
||||||
|
HttpOnly: true,
|
||||||
|
})
|
||||||
|
fmt.Printf("Tokent: %s", token)
|
||||||
|
http.Redirect(w, r, oauth2Config.AuthCodeURL("state", oauth2.AccessTypeOffline), http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var claims struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
PreferredUsername string `json:"preferred_username"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
if err := token.Claims(&claims); err != nil {
|
||||||
|
http.Error(w, "Failed to parse claims", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
email := claims.Email
|
||||||
|
if email == "" {
|
||||||
|
email = strings.Split(claims.PreferredUsername, "@")[0]
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "Welcome! Logged in as: %s", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
code := r.URL.Query().Get("code")
|
||||||
|
|
||||||
|
token, err := oauth2Config.Exchange(ctx, code)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Token exchange failed", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rawIDToken, ok := token.Extra("id_token").(string)
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, "No id_token found", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify and set as cookie
|
||||||
|
_, err = verifier.Verify(ctx, rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to verify ID token", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set token as cookie
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: "id_token",
|
||||||
|
Value: rawIDToken,
|
||||||
|
Path: "/",
|
||||||
|
HttpOnly: true,
|
||||||
|
})
|
||||||
|
http.Redirect(w, r, "/", http.StatusFound)
|
||||||
|
}
|
Reference in New Issue
Block a user