I have created an oauth2 app using MS app registration and the plan is to authenticate clients using this app registration.
I am running the following code to authenticate a person using his MS Azure creds.
package main
import (
"context"
"fmt"
"log"
"net/http"
"golang.org/x/oauth2"
"golang.org/x/oauth2/microsoft"
)
func main() {
clientID := "292def6c-3c4c-42ed-9ef2-XXXXXXXXXXXX"
redirectURL := "http://localhost:8000/callback"
scopes := []string{"User.Read"} // Specify the required scopes
conf := &oauth2.Config{
ClientID: clientID,
RedirectURL: redirectURL,
Scopes: scopes,
Endpoint: microsoft.AzureADEndpoint("4d16a70b-76a1-4ad7-944a-XXXXXX"), // Replace with your Azure AD tenant ID
}
// Generate authorization URL
authURL := conf.AuthCodeURL("state", oauth2.AccessTypeOffline)
fmt.Printf("Open the following URL in your browser:\n\n%s\n\n", authURL)
fmt.Println("After authentication, you will be redirected to the specified redirect URL.")
m := http.NewServeMux()
server := &http.Server{
Addr: ":8000",
Handler: m,
}
// Start a local HTTP server to listen for the callback
m.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
// Exchange authorization code for access token
token, err := conf.Exchange(context.Background(), code)
if err != nil {
log.Fatal("Token exchange error:", err)
}
// prints the access token to terminal
fmt.Println("Token:", token.AccessToken)
// Use the token for further API requests
// Example: call the Microsoft Graph API to retrieve the user's profile
client := conf.Client(context.Background(), token)
resp, err := client.Get("https://graph.microsoft.com/v1.0/me")
if err != nil {
log.Fatal("API request error:", err)
}
defer resp.Body.Close()
fmt.Println("\nAuthentication successful! You can close the browser.")
w.Write([]byte("Authentication successful! You can close the browser."))
})
log.Fatal(server.ListenAndServe())
}
The above code prints a URL for authentication and starts a server to catch the response and prints the access token in the terminal. Next,I head over to https://login.microsoftonline.com/4d16a70b-76a1-4ad7-944a-13513528982b/discovery/v2.0/keys and match the right public using the access token kid
key. The idea is that the access token must be signed by one of the public keys from the above URL and the public key can be using the kid
key from the access token. Now I run the following program which should ideally verify the key.
func main() {
// Replace 'YOUR_ACCESS_TOKEN' with the actual access token you want to verify
tokenString := "ENTIRE_ACCESS_TOKEN"
key := "-----BEGIN CERTIFICATE-----\nTHE_PUBLIC_KEY\n-----END CERTIFICATE-----"
// Define the Microsoft AAD public key or certificate URL
// You may need to update the URL based on your specific AAD configuration
// microsoftKeysURL := "https://login.microsoftonline.com/common/discovery/v2.0/keys"
// Parse the token without verifying the signature
t, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return jwt.ParseRSAPublicKeyFromPEM([]byte(key))
})
if err != nil {
log.Fatal("Failed to parse token:", err)
}
fmt.Println(t)
}
The above program fails with the following error
Failed to parse token:crypto/rsa: verification error
There could be two reasons for the error
- I am doing something wrong programmatically.
- I am using wrong public key for the verification, which would be very hard to believe for me because I am matching the public key using kid, i get one key matched out of all those public keys and that should be the right key.