You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go调用Asana OAuth接口返回400错误,Node.js代码正常运行

Asana OAuth令牌请求返回400错误的排查

我有一段可正常运行的Node.js代码,用于生成Asana OAuth授权链接以及获取访问令牌:

export const createAsanaAuthUrl = () => {
  // Store this in DB
  const codeChallenge = crypto
    .createHash('sha256')
    .update(CODE_VERIFIER)
    .digest('base64')
    .replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_');
  const udid = uuidv4();
  const url = `${ASANA_AUTH_BASE}/oauth_authorize?client_id=${ASANA_CLIENT_ID}&redirect_uri=${ASANA_REDIRECT_URL}&response_type=code&state=${udid}&code_challenge_method=S256&code_challenge=${codeChallenge}`;
  return url;
};


export const getAsanToken = async (code: string) => {
  const params = {
    grant_type: 'authorization_code',
    client_id: ASANA_CLIENT_ID,
    client_secret: ASANA_CLIENT_SECRET,
    redirect_uri: ASANA_REDIRECT_URL,
    code,
    code_verifier: CODE_VERIFIER,
  };
  const { refresh_token, access_token } = (
    await axios.post(`${ASANA_AUTH_BASE}/oauth_token`, params, {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    })
  ).data;
};

但对应的Go代码调用Asana的/oauth_token接口时返回400错误,代码如下:

func CreateAsanaAuthUrl(env *config.Env) string {
    hash := sha256.Sum256([]byte(constants.AsanaCodeVerifier))
    codeChallenge := base64.URLEncoding.EncodeToString(hash[:])
    codeChallenge = strings.TrimRight(codeChallenge, "=")
    udid := uuid.New().String()
    url := fmt.Sprintf("%s/oauth_authorize?client_id=%s&redirect_uri=%s&response_type=code&state=%s&code_challenge_method=S256&code_challenge=%s", constants.AsanaAuthBase, env.AsanaClientId, env.AsanaRedirectUrl, udid, codeChallenge)
    return url
}

func GetAsanaToken (args domain.GetTokenArgs)  (*domain.ServiceAuthReturn, error) {
    env := args.Env
    params := TokenAsanaRequest{
    GrantType:    "authorization_code",
    ClientID:     env.AsanaClientId,
    ClientSecret: env.AsanaClientSecret,
    RedirectURI:  env.AsanaRedirectUrl,
    Code:         args.Code,
    CodeVerifier: constants.AsanaCodeVerifier,
    }
    fmt.Printf("p: %+v\n", params)
    url := constants.AsanaAuthBase + "/oauth_token"
    reqBody, err := json.Marshal(params)
    fmt.Printf("Url: %v", url)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(reqBody))
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        log.Println("Error", err) // log
        return nil, fmt.Errorf("error sending Asana auth request: %s", err.Error())
    }
    defer resp.Body.Close()
    if resp.StatusCode >= 400 {
    // server returned an error
        errorBody, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            log.Println("Error:", err) // log
            return nil, fmt.Errorf("error reading error response body: %s", err.Error())
        }
        log.Printf("Error response body: %s", string(errorBody)) // log error response body
        return nil, fmt.Errorf("error getting asana auth token %d", resp.StatusCode)
    }

    var result struct {
        AccessToken string `json:"access_token"`
        RefreshToken string `json:"refresh_token"`
    }
}

错误发生在日志打印错误响应体的位置,状态码为400,请问可能是什么原因导致的?


原因分析

  • 请求体格式不匹配:Go代码中用json.Marshal生成JSON格式的请求体,但设置的Content-Type是application/x-www-form-urlencoded,二者完全矛盾。Asana的OAuth令牌接口要求接收表单格式的请求体(如grant_type=authorization_code&client_id=xxx),但你实际发送的是JSON,服务器无法解析,直接返回400错误。

  • Node.js与Go的行为差异:Node.js中的axios库,当传入普通对象并设置Content-Type: application/x-www-form-urlencoded时,会自动将对象序列化为表单格式;但Go没有这种自动转换逻辑,手动用JSON序列化后发送,就会出现格式不兼容的问题。

  • 字段名可能不匹配:如果TokenAsanaRequest结构体没有添加表单序列化标签(如form:"grant_type"),即使用表单序列化,字段名可能会变成驼峰式(如GrantType),而Asana接口需要的是下划线格式的字段名,也会导致解析失败。

修复方案

修改Go代码中的请求体生成逻辑,将参数序列化为表单格式:

// 替换原有的json.Marshal相关代码
form := url.Values{}
form.Set("grant_type", "authorization_code")
form.Set("client_id", env.AsanaClientId)
form.Set("client_secret", env.AsanaClientSecret)
form.Set("redirect_uri", env.AsanaRedirectUrl)
form.Set("code", args.Code)
form.Set("code_verifier", constants.AsanaCodeVerifier)
reqBody := strings.NewReader(form.Encode())

// 创建请求时使用表单体
req, err := http.NewRequest("POST", url, reqBody)
if err != nil {
    log.Println("Error creating request:", err)
    return nil, fmt.Errorf("error creating Asana auth request: %s", err.Error())
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

如果想用结构体直接转表单,可以使用第三方库(如github.com/go-playground/form),但手动构建url.Values是最直接的修复方式。


内容的提问来源于stack exchange,提问作者Alwaysblue

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 19:47:24