Search code examples
gooauth-2.0go-testing

golang.org/x/oauth2 oauth2.Config.Endpoint.TokenURL mock: missing access_token


I want to test my oauth2 client and use a mock for TokenURL endpoint. The mock responds with access_token but there is always an error with missing access_token.

I am using golang.org/x/oauth2 and default go testing package.

My mock server:

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.URL.Path == "/token" {
            // Mock the token endpoint
            json.NewEncoder(w).Encode(struct {
                AccessToken  string    `json:"access_token"`
                TokenType    string    `json:"token_type"`
                RefreshToken string    `json:"refresh_token"`
                Expiry       time.Time `json:"expiry"`
            }{
                AccessToken:  "access-token",
                TokenType:    "Bearer",
                RefreshToken: "refresh-token",
                Expiry:       time.Now().Add(2 * time.Hour),
            })
        }
    }))
    defer server.Close()
    mockConfig.Endpoint.TokenURL = server.URL + "/token"

example: I want to refresh access token

tokenSource := a.config.TokenSource(ctx, &oauth2.Token{
            RefreshToken: session.RefreshToken,
        })

error while refreshing token:

oauth2: server response missing access_token

I have already checked/tried

  • the mock response, if access_token is given
  • the code of oauth2 package to see when this error occurs, but have not found any further information
  • verified the right config parameter (oauth2.Config.Endpoint.TokenURL)

The code is running with my oauth2 provider, so it is just a testing issue.

Thanks!


Solution

  • You need to set the Content-Type header to application/jsonso that the client can properly interpret the response body.

    Here is a complete working example.

    func MockOAuth2Server() *http.Server {
        mux := http.NewServeMux()
        mux.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
            // Always redirect to the callback URL with a fixed code
            http.Redirect(w, r, r.URL.Query().Get("redirect_uri")+"?code=mockcode"+"&state="+r.URL.Query().Get("state"), http.StatusFound)
        })
        mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
            // Set the content type to JSON 
            w.Header().Set("Content-Type", "application/json")
    
            idToken, err := generateMockIDToken()
            if err != nil {
                slog.Error("Error generating mock id token", "error", err.Error())
            }
            w.Write([]byte(`{"access_token": "mocktoken", "id_token": "` + idToken + `", "token_type": "bearer"}`))
        })
    
        mux.HandleFunc("/introspect", func(w http.ResponseWriter, r *http.Request) {
            // Always return that the token is active
            w.Header().Set("Content-Type", "application/json")
            w.Write([]byte(`{"active": true}`))
        })
        return &http.Server{
            Addr:    "localhost:9999",
            Handler: mux,
        }
    }