I am trying to connect to IBM Watson's speech to text WebSocket with a new account, that has IAM authorization only.
My WS endpoint is wss://stream-fra.watsonplatform.net/text-to-speech/api/v1/recognize and I get an access token via https://iam.bluemix.net/identity/token. Now when I open the socket connection with Authorization header with value Bearer token I get a bad handshake response: websocket: bad handshake, Unauthorized 401. Language is Go.
Am I doing something wrong or it is not possible to connect to Watson's speech to text WebSocket without username/password authentication i.e. the deprecated watson-token?
EDIT:
Code to open WebSocket:
headers := http.Header{}
headers.Set("Authorization", "Bearer " + access_token)
conn, resp, err := websocket.DefaultDialer.Dial("wss://stream-fra.watsonplatform.net/text-to-speech/api/v1/recognize", headers)
I have also tried basic authorization with apikey:**api_key** and the result is the same: 401.
EDIT 2:
Code to get the access token (based on the Watson Swift and Python SDKs) which succeeds and returns access and refresh tokens:
func getWatsonToken(apiKey string) (string, error) {
// Base on the Watson Swift and Python SDKs
// https://github.com/watson-developer-cloud/restkit/blob/master/Sources/RestKit/Authentication.swift
// https://github.com/watson-developer-cloud/python-sdk/blob/master/watson_developer_cloud/iam_token_manager.py
const tokenUrl = "https://iam.bluemix.net/identity/token"
form := url.Values{}
form.Set("grant_type", "urn:ibm:params:oauth:grant-type:apikey")
form.Set("apikey", apiKey)
form.Set("response_type", "cloud_iam")
// Token from simple "http.PostForm" does not work either
//resp, err := http.PostForm(tokenUrl, form)
req, err := http.NewRequest(http.MethodPost, tokenUrl, nil)
if err != nil {
log.Printf("could not create HTTP request to get Watson token: %+v", err)
return "", nil
}
header := http.Header{}
header.Set("Content-Type", "application/x-www-form-urlencoded")
header.Set("Accept", "application/json")
// "Yng6Yng=" is "bx:bx"
header.Set("Authorization", "Basic Yng6Yng=")
req.Header = header
req.Body = ioutil.NopCloser(bytes.NewReader([]byte(form.Encode())))
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Printf("problem executing HTTP request to get Watson token: %+v", err)
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", errors.New(fmt.Sprintf("failed to get Watson token: %d", resp.StatusCode))
}
jsonBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("problem reading Watson token from response body: %+v", err)
}
tokenResponse := &struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
ExpiresIn int64 `json:"expires_in"`
Expiration int64 `json:"expiration"`
}{}
err = json.Unmarshal(jsonBody, tokenResponse)
if err != nil {
log.Printf("could not parse Watson token response: %+v", err)
return "", err
}
return tokenResponse.AccessToken, err
}
I made an error in the endpoint and used the text-to-speech instead of the speech-to-text. With the correct URL the WebSocket API works.
wss://stream-fra.watsonplatform.net/text-to-speech/api/v1/recognize should be wss://stream-fra.watsonplatform.net/speech-to-text/api/v1/recognize
Related
I have a go api, and we are trying to send a message on a channel thanks to oAuth2.
So far, we retrieved the token and we can display some user information (email, username etc...)
we are trying to retrieve some message, but we have an "unauthorized" error.
I read some documentation about permissions, i think i have to do somethings with header, but i'm totally new to golang.
can you help me? :)
this is the code :
func HelloDiscord(w http.ResponseWriter, r *http.Request){
conf := &oauth2.Config{
RedirectURL: "http://localhost:8080/auth/discord",
ClientID: "clientid",
ClientSecret: "clientsecret",
Scopes: []string{discord.ScopeIdentify},
Endpoint: discord.Endpoint,
}
token, _ := conf.Exchange(context.Background(), r.FormValue("code"))
fmt.Println(token)
message, err := conf.Client(context.Background(), token).Get("https://discord.com/api/channels/1021410887191507004/messages")
fmt.Println("message;")
fmt.Println(message)
if err != nil || message.StatusCode != 200 {
w.WriteHeader(http.StatusInternalServerError)
if err != nil {
w.Write([]byte(err.Error()))
} else {
w.Write([]byte(message.Status))
}
return
}
defer message.Body.Close()
body, err := ioutil.ReadAll(message.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
w.Write(body)
}
thanks a lot
I'm writing in Go and testing an Alexa skill and looking to retrieve the user's Amazon account name and other fields. I have the permissions set correctly in the alexa developer console. I have ALL of the permissions enabled.
My functions are running in AWS Lambda and Alexa tests just fine with other functions.
Here is the code:
func callAmazonAPI(apiEndpoint string, apiToken string) (string, error) {
//url := apiEndpoint + "/v2/accounts/~current/settings/Profile.name"
url := "https://api.amazonalexa.com/v2/accounts/~current/settings/Profile.name"
// Create a Bearer string by appending string access token
var bearer = "Bearer " + apiToken
// Create a new request using http
req, err := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", bearer)
req.Header.Add("Accept", "application/json")
req.Header.Add("Host", "api.amazonalexa.com")
// Send req using http Client
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "Error in callAMazonAPI!", err
}
body, _ := ioutil.ReadAll(resp.Body)
return string([]byte(body)), err
}
It is not returning an error. It is returning this:
{"code":"ACCESS_DENIED","message":"Access denied with reason: ACCESS_NOT_REQUESTED"}
According to the Alexa documentation I should have access to account information.
I am exploring OAuth2 authentication and set up a server that authenticates with Github. I followed this example, and was able to get it working. I wanted to continue on with some of the suggestions and implement a basic session token system and make the Github calls from my server as opposed to sending the Authorization token to the client.
Here is my slightly modified /oauth/redirect handler
func oauthRedirectHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("/oauth/redirect")
err := r.ParseForm()
if err != nil {
fmt.Fprintf(os.Stdout, "could not parse query: %+v", err)
w.WriteHeader(http.StatusBadRequest)
}
code := r.FormValue("code")
reqURL := fmt.Sprintf("https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s", clientID, clientSecret, code)
req, err := http.NewRequest(http.MethodPost, reqURL, nil)
if err != nil {
fmt.Fprintf(os.Stdout, "could not create HTTP request: %v", err)
w.WriteHeader(http.StatusBadRequest)
}
req.Header.Set("accept", "application/json")
res, err := httpClient.Do(req)
if err != nil {
fmt.Fprintf(os.Stdout, "could not send HTTP request: %+v", err)
w.WriteHeader(http.StatusInternalServerError)
}
defer res.Body.Close()
var t oAuthAccessResponse
if err := json.NewDecoder(res.Body).Decode(&t); err != nil {
fmt.Fprintf(os.Stdout, "could not parse JSON response: %+v", err)
w.WriteHeader(http.StatusBadRequest)
}
newSession := sessionTracker{
AccessToken: accessToken,
TimeOut: time.Now().Add(time.Minute * 15),
}
sessionToken := uuid.New().String()
sessionTrackerCache[sessionToken] = newSession
http.SetCookie(w, &http.Cookie{
Name: sessionTokenConst,
Value: sessionToken,
Expires: newSession.TimeOut,
Domain: "localhost",
})
http.Redirect(w, r, "/welcome.html", http.StatusFound)
}
It redirects to the welcome page with an attached cookie that includes my SessionToken id.
Here is my welcomeHandler
func welcomeHandler(w http.ResponseWriter, req *http.Request) {
fmt.Println("/welcome")
cookie, err := req.Cookie(sessionTokenConst)
if err != nil {
fmt.Fprintf(os.Stdout, "no cookie attached: %+v", err)
w.WriteHeader(http.StatusBadRequest)
return
}
dat, err := ioutil.ReadFile("./public/welcome.html")
if err != nil {
fmt.Fprintf(os.Stdout, "could not read welcome page: %+v", err)
w.WriteHeader(http.StatusInternalServerError)
}
fmt.Fprintf(w, string(dat))
}
Observing my browser's network tab, I authenticate with Github and my server is able to see the authorization token. The redirect response at the end of the oauthRedirectHandler contains the cookie with the SessionID. My issue lies in the fact that the browser does not seem to attach the token on the GET call to /welcome.html. I can confirm this in both the browser and in the welcomeHandler.
This is all hosted locally.
I'm not sure if this is a issue with my server, my browser, or if my understanding that cookies are applied by the browser to any future client requests until the cookie expiration date is wrong.
Any help is appreciated!
Browsers default the cookie path to the request path. Set the path to "/" to make the cookie available across the site.
Do not set the domain unless you specifically have a reason to do so (thank you Volker for noting that).
http.SetCookie(w, &http.Cookie{
Name: sessionTokenConst,
Value: sessionToken,
Path: "/", // <--- add this line
Expires: newSession.TimeOut,
// Domain: "localhost", <-- Remove this line
})
I want to implement an Ebay Browse API client credentials grant flow in Go using the oauth2 package. Here is my conf struct:
conf := clientcredentials.Config{
ClientID: "My Client ID",
ClientSecret: "My client Secret",
Scopes: []string{"https://api.ebay.com/oauth/api_scope"},
TokenURL: "https://api.sandbox.ebay.com/identity/v1/oauth2/token",
EndpointParams: url.Values{"redirect_uri": {"the RuName"}},
}
Then I tried to make a request by creating an *http.Request:
req, err := http.NewRequest("GET", "https://api.sandbox.ebay.com/buy/browse/v1/item/v1|202117468662|0?fieldgroups=COMPACT", nil)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
client := conf.Client(ctx)
res, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
But then I get:
{
"errors" : [ {
"errorId" : 1003,
"domain" : "OAuth",
"category" : "REQUEST",
"message" : "Token type in the Authorization header is invalid:Application",
"longMessage" : "Token type in the Authorization header is invalid:Application"
} ]
}
It turned out, that when making the request to the resource server Go's package sets the Authorization header as:
Authorization: Application Access Token token_string
So, I tried to set the token type to Bearer manually by doing:
tok, err := conf.Token(ctx)
if err != nil {
log.Fatal(err)
}
client := conf.Client(ctx)
req, err := http.NewRequest("GET", "https://api.sandbox.ebay.com/buy/browse/v1/item/v1|202117468662|0?fieldgroups=COMPACT", nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Authorization", "Bearer " tok.AccessToken)
res, err := client.Do(req)
But I continue getting the same error.
So, there is a problem (bug?) with the Ebay's implementation of the OAuth2.0 spec and I had to reimplement the TokenSource of the oauth2/clientcredentials package to set the token type to Bearer not Application Access Token.
We have an oauth2 endpoint that seems to require a client_credentials token to use as a Bearer for the initial code to token exchange process. I have successfully obtained the token for that.
However, in go's current implementation of oauth2 client lib, the Exchange() function (see: Exchange which eventually calls RetrieveToken)
It doesn't add an "Authentication: Bearer " header with a token I can slip in during the Exchange. It can however add a Basic auth header. Our implementation does not currently support basic auth though.
If possible, I'd like to make it add the header without modifying the source code to to oauth2 package.
It would appear if I call oauth2.RegisterBrokenAuthHeaderProvider it might skip the attempt to add a basic auth header which is needed in my case since that'll not work.
Then finally the call to do the http request which is setup as a POST.
Code:
func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, v url.Values) (*Token, error) {
hc, err := ContextClient(ctx)
if err != nil {
return nil, err
}
bustedAuth := !providerAuthHeaderWorks(tokenURL)
if bustedAuth {
if clientID != "" {
v.Set("client_id", clientID)
}
if clientSecret != "" {
v.Set("client_secret", clientSecret)
}
}
req, err := http.NewRequest("POST", tokenURL, strings.NewReader(v.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if !bustedAuth {
req.SetBasicAuth(url.QueryEscape(clientID), url.QueryEscape(clientSecret))
}
r, err := ctxhttp.Do(ctx, hc, req)
if err != nil {
return nil, err
}
// rest of code ...
}
the ctxhttp.Do can take a context. I've been reading about these but I don't understand how they work. Can I use them to add my header or any other headers I might require?
I'm not sure if this is the most optimal way of doing it, but it seems to work. This is the redirect callback handler that is called as a result of visiting the an auth endpoint.
// Create a custom tokenSource type
type tokenSource struct{ token *oauth2.Token }
func (t *tokenSource) Token() (*oauth2.Token, error) {
fmt.Println("TokenSource!", t.token)
return t.token, nil
}
func handleCallback(w http.ResponseWriter, r *http.Request) {
state := r.FormValue("state")
if state != oauthStateString {
fmt.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
code := r.FormValue("code")
// Create a tokenSource and fill in our application Bearer token
ts := &tokenSource{
token: &oauth2.Token{
AccessToken: appToken.AccessToken,
TokenType: appToken.TokenType,
},
}
tr := &oauth2.Transport{
Source: ts,
}
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, &http.Client{
Transport: tr,
})
// myConfig defined previously and in the usual way. Look at the examples.
token, err := myConfig.Exchange(ctx, code)
if err != nil {
fmt.Printf("Code exchange failed with '%s'\n", err)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
fmt.Printf("token: %+v", token)
// token can now be used.
}