Skip to content

Instantly share code, notes, and snippets.

@jordanorelli
Last active December 10, 2015 14:48
Show Gist options
  • Save jordanorelli/4449996 to your computer and use it in GitHub Desktop.
Save jordanorelli/4449996 to your computer and use it in GitHub Desktop.
twitter oauth signing example in Go
var alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
// generates a random string of fixed size
func nonce(size int) string {
buf := make([]byte, size)
for i := 0; i < size; i++ {
buf[i] = alpha[rand.Intn(len(alpha)-1)]
}
return string(buf)
}
// a Twitter.Client should be used for handling all communication to the
// Twitter API.
type Client struct {
http.Client
Key string
Secret string
OauthCallbackURL string
}
func (c *Client) sign(req *http.Request, token, secret string) {
vals := map[string]string{
"oauth_consumer_key": c.Key,
"oauth_nonce": nonce(40),
"oauth_signature_method": "HMAC-SHA1",
"oauth_timestamp": strconv.FormatInt(time.Now().Unix(), 10),
"oauth_version": "1.0",
}
if token == "" {
vals["oauth_callback"] = url.QueryEscape(c.OauthCallbackURL)
} else {
vals["oauth_token"] = token
}
// add the querystring values. The actual RFC describing the querystring
// allows for multiple keys, but the Twitter API doesn't actually allow
// that, so I just take the first string from every slice
qvals := map[string][]string(req.URL.Query())
for k, v := range qvals {
if len(v) > 0 {
vals[k] = v[0]
}
}
// make an alphabetical list of the keys in the auth header vals
keys := make([]string, len(vals))
i := 0
for k, _ := range vals {
keys[i] = k
i++
}
sort.Strings(keys)
// serialize the map as a string of url encoded key=val pairs.
// i.e., join them first, then encode them.
var baseParts []string
for _, key := range keys {
baseParts = append(baseParts, fmt.Sprintf("%s=%s", key, vals[key]))
}
// the signing key is the ampersand-joined, urlencoded app and user secrets.
// i.e., encode them first, then join them.
signingKey := fmt.Sprintf("%s&%s", url.QueryEscape(c.Secret), url.QueryEscape(secret))
h := hmac.New(sha1.New, []byte(signingKey))
// each of the following three parts is urlencoded, and then joined with ampersands:
// the request method, the absolute request path (without query string),
// and the ampersand-joined set of key-value pairs in the auth header map.
io.WriteString(h, strings.Join([]string{
url.QueryEscape(req.Method),
url.QueryEscape(req.URL.Scheme + "://" + req.URL.Host + req.URL.Path),
url.QueryEscape(strings.Join(baseParts, "&")),
}, "&"))
// alright, now we have the hmac signature.
vals["oauth_signature"] = url.QueryEscape(base64.StdEncoding.EncodeToString(h.Sum(nil)))
// but now we have to re-sort the list to include the new oauth_signature=whatever pair.
keys = make([]string, len(vals))
i = 0
for k, _ := range vals {
keys[i] = k
i++
}
sort.Strings(keys)
// and now build the actual header string. These values are each
// key="value" strings, note the ampersand. This time, they're joined by a
// comma, followed by either a space or a newline, and are not urlencoded.
var s []string
for _, key := range keys {
s = append(s, fmt.Sprintf(`%s="%s"`, key, vals[key]))
}
// fuck my life, this spec is so fucking convoluted, it's such a waste of time.
req.Header.Set("Authorization", "OAuth "+strings.Join(s, ", "))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment