go-oidc icon indicating copy to clipboard operation
go-oidc copied to clipboard

Microsoft nonce and state doesn't match

Open onattech opened this issue 3 years ago • 1 comments

Hi, I am trying to figure out open-id connect with Microsoft. I am getting nonce and state doesn't match errors. Cookie in the browser doesn't seem to update. If I remove those checks, it seems to work. Can someone point out what am I doing wrong or maybe give a working example for Microsoft. Here's my code below Added comments to the lines that I changed from the original example.

func main() {
	godotenv.Load()
	clientID := os.Getenv("MICROSOFT_OAUTH2_CLIENT_ID")         // <==
	clientSecret := os.Getenv("MICROSOFT_OAUTH2_CLIENT_SECRET") // <==

	ctx := context.Background()

	discoveryBaseURL := "https://login.microsoftonline.com/common/v2.0"                        // <==
	issuerURL := "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a500b66dad/v2.0" // <==

	ctx = oidc.InsecureIssuerURLContext(ctx, issuerURL) // <==

	// Provider will be discovered with the discoveryBaseURL, but use issuerURL
	// for future issuer validation.
	provider, err := oidc.NewProvider(ctx, discoveryBaseURL) // <==

	if err != nil {
		log.Fatal(err)
	}
	oidcConfig := &oidc.Config{
		ClientID: clientID,
	}
	verifier := provider.Verifier(oidcConfig)

	config := oauth2.Config{
		ClientID:     clientID,
		ClientSecret: clientSecret,
		Endpoint:     provider.Endpoint(),
		RedirectURL:  "http://localhost:5556/auth/microsoft/callback", // <==
		Scopes:       []string{oidc.ScopeOpenID, "profile", "email"},
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		state, err := randString(16)
		if err != nil {
			http.Error(w, "Internal error", http.StatusInternalServerError)
			return
		}
		nonce, err := randString(16)
		if err != nil {
			http.Error(w, "Internal error", http.StatusInternalServerError)
			return
		}
		setCallbackCookie(w, r, "state", state)
		setCallbackCookie(w, r, "nonce", nonce)

		http.Redirect(w, r, config.AuthCodeURL(state, oidc.Nonce(nonce)), http.StatusFound)
	})

	http.HandleFunc("/auth/microsoft/callback", func(w http.ResponseWriter, r *http.Request) { // <==
		state, err := r.Cookie("state")
		if err != nil {
			http.Error(w, "state not found", http.StatusBadRequest)
			return
		}
		if r.URL.Query().Get("state") != state.Value {
			http.Error(w, "state did not match", http.StatusBadRequest)
			return
		}

		oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
		if err != nil {
			http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
			return
		}
		rawIDToken, ok := oauth2Token.Extra("id_token").(string)
		if !ok {
			http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
			return
		}
		idToken, err := verifier.Verify(ctx, rawIDToken)
		if err != nil {
			http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
			return
		}

		nonce, err := r.Cookie("nonce")
		if err != nil {
			http.Error(w, "nonce not found", http.StatusBadRequest)
			return
		}
		if idToken.Nonce != nonce.Value {
			http.Error(w, "nonce did not match", http.StatusBadRequest)
			return
		}

		oauth2Token.AccessToken = "*REDACTED*"

		resp := struct {
			OAuth2Token   *oauth2.Token
			IDTokenClaims *json.RawMessage // ID Token payload is just JSON.
		}{oauth2Token, new(json.RawMessage)}

		if err := idToken.Claims(&resp.IDTokenClaims); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		data, err := json.MarshalIndent(resp, "", "    ")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Write(data)
	})

	log.Printf("listening on http://%s/", "127.0.0.1:5556")
	log.Fatal(http.ListenAndServe("localhost:5556", nil)) // <===
}

onattech avatar Feb 12 '23 13:02 onattech

I stuck with the same problem

richie-tt avatar Apr 01 '24 15:04 richie-tt