我正在尝试在 Go 中的 CLI 上执行设备授权流程。我已按照 https://auth0.com/blog/securing-a-python-cli-application-with-auth0/ 中的步骤在 Auth0 中设置我的应用程序。成功请求设备代码后,我尝试获取请求令牌。
// Gets a request token.
func (loginJob *LoginJob) GetRequestToken(deviceCodeData loginResponse.LResponse) error {
//Setup an http request to retreive a request token.
url := loginJob.Domain + "/oauth/token"
method := "POST"
payload := strings.NewReader("grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&device_code=" +
deviceCodeData.DeviceCode + "&client_id=" + loginJob.ClientID)
client := &http.Client{}
req, reqErr := http.NewRequest(method, url, payload)
if reqErr != nil {
fmt.Println(reqErr)
return reqErr
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
authenticate := false
for !authenticate {
authenticate = PollRequestTokenStatus(req, client)
time.Sleep(time.Duration(deviceCodeData.Interval) * time.Second)
}
return nil
}
func PollRequestTokenStatus(req *http.Request, client *http.Client) bool {
res, resErr := client.Do(req)
if resErr != nil {
log.Panic(resErr)
return true
}
defer res.Body.Close()
body, ReadAllErr := io.ReadAll(res.Body)
if ReadAllErr != nil {
fmt.Println(ReadAllErr)
return true
}
fmt.Println("res.Body: ")
fmt.Println(string(body))
if res.StatusCode == 200 {
fmt.Println("Authenticated!")
fmt.Println("- Id Token: ")
return true
} else if res.StatusCode == 400 {
fmt.Println("res.StatusCode: ")
fmt.Print(res.StatusCode)
return false
} else {
fmt.Println("res.StatusCode: ")
fmt.Print(res.StatusCode)
}
return false
}
我的想法是,我以特定的时间间隔轮询 Auth0 来获取请求令牌。我第一次投票时,收到 403 Forbidden:
{"error":"authorization_pending","error_description":"User has yet to authorize device code."}
但是,在随后的民意调查中,我收到 400 Bad Request:
<html>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<hr><center>cloudflare</center>
</body>
</html>
我不确定为什么会发生这种情况。我尝试过使用 Postman 手动轮询 Auth0,并且我总是设法使用 Postman 避免错误 400。 我该如何解决这个问题?
更新:由于某种原因,Go 每当调用 *http.Client.Do() 时都会重置所有请求标头,所以我尝试将请求构造移到 for 循环内。现在我的代码如下所示:
// Gets a request token.
func (loginJob *LoginJob) GetRequestToken(deviceCodeData loginResponse.LResponse) error {
// Setup an http request to retreive a request token.
url := loginJob.Domain + "/oauth/token"
method := "POST"
payload := strings.NewReader("grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&device_code=" +
deviceCodeData.DeviceCode + "&client_id=" + loginJob.ClientID)
authenticate := false
var pollErr error
for !authenticate {
authenticate, pollErr = loginJob.PollRequestTokenStatus(url, method, payload, deviceCodeData)
if pollErr != nil {
log.Panic(pollErr)
}
time.Sleep(time.Duration(deviceCodeData.Interval) * time.Second)
}
return nil
}
func (loginJob *LoginJob) PollRequestTokenStatus(url string, method string, payload *strings.Reader, deviceCodeData loginResponse.LResponse) (bool, error) {
// Setup an http request to retreive a request token.
client := &http.Client{
Timeout: time.Second * 10,
}
req, reqErr := http.NewRequest(method, url, payload)
if reqErr != nil {
fmt.Println(reqErr)
return false, reqErr
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
res, resErr := client.Do(req)
if resErr != nil {
fmt.Println(resErr)
return false, resErr
}
defer res.Body.Close()
fmt.Println("res.StatusCode:")
fmt.Println(res.StatusCode)
if res.StatusCode == 200 {
fmt.Println("Authenticated!")
fmt.Println("- Id Token: ")
return true, nil
} else if res.StatusCode == 400 {
return true, nil
}
return false, nil
}
我现在有一个不同的问题。我在后续民意调查中没有返回错误 400,而是收到 401:
{"error":"access_denied","error_description":"Unauthorized"}
结果我需要在 for 循环中实例化 strings.Reader 。我的代码现在如下。
// Gets a request token.
func (loginJob *LoginJob) GetRequestToken() error {
url := loginJob.Domain + "/oauth/token"
method := "POST"
authenticate := false
var pollErr error
//Keep polling Auth0 for a request token until status 200 or until invalid grant
for !authenticate {
authenticate, pollErr = loginJob.PollRequestTokenStatus(url, method)
if pollErr != nil {
log.Panic(pollErr)
}
//Sleep for the interval duration in the device code data. If we poll too fast, Auth0 will give 429 status.
time.Sleep(time.Duration(loginJob.DeviceCodeData.Interval) * time.Second)
}
return nil
}
func (loginJob *LoginJob) PollRequestTokenStatus(url string, method string) (bool, error) {
//Construct a new Reader. This must be done within this function, or else Go will read to the end of the REader and not
//properly construct our http request.
payload := strings.NewReader("grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&device_code=" +
loginJob.DeviceCodeData.DeviceCode + "&client_id=" + loginJob.ClientID)
//Create a new http Client. This is done within this function because Go will not erase previous Client settings if we
//pass a client instance as a parameter, which will mess up our post request.
client := &http.Client{
Timeout: time.Second * 10,
}
//Construct a new http request. This must be done within this function because Go will nuke the request headers after every
//call of &http.Client.Do().
req, reqErr := http.NewRequest(method, url, payload)
if reqErr != nil {
fmt.Println(reqErr)
return false, reqErr
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
res, resErr := client.Do(req)
if resErr != nil {
fmt.Println(resErr)
return false, resErr
}
defer res.Body.Close()
fmt.Println("res.StatusCode:")
fmt.Println(res.StatusCode)
if res.StatusCode == 200 {
fmt.Println("Authenticated!")
fmt.Println("- Id Token: ")
return true, nil
} else if res.StatusCode == 400 {
return true, nil
}
return false, nil
}