Last active
December 10, 2015 14:48
-
-
Save jordanorelli/4449996 to your computer and use it in GitHub Desktop.
twitter oauth signing example in Go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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