This project is under active development, and has yet to reach 1.0. As such the API may change.
Require the library with a convenient alias that we can make use of later.
(require '[oauth.one :as one])
Create a consumer using the credentials provided by (in this example) Twitter. The consumer holds on to important URLs, tokens and information about how to generate OAuth requests. We’ll pull our consumer key and secret from environment variables to avoid adding sensitive credentials to our repository.
(def consumer
(one/make-consumer
{:access-uri "https://api.twitter.com/oauth/access_token"
:authorize-uri "https://api.twitter.com/oauth/authorize"
:callback-uri "http://127.0.0.1:3000/oauth/callback"
:key (System/getenv "TWITTER_CONSUMER_KEY")
:request-uri "https://api.twitter.com/oauth/request_token"
:secret (System/getenv "TWITTER_CONSUMER_SECRET")
:signature-algo :hmac-sha1}))
The :callback-uri
is a local address we can use for testing purposes. All of
the above can and should be pulled from whatever configuration system you’re
using your application (I’d recommend a combination of environ and Schema).
Check out my lein-template for an example of how to pull your configuration from
environment variables and both validate and coerce the data.
Now that you have a consumer you can build a request to request a token.
(one/request-token-request consumer)
request-token-request
returns a clj-http
compatible hash-map that can be
passed to your favourite HTTP library quite easily. An example request map looks
something like this:
{:request-method :post,
:url "https://api.twitter.com/oauth/request_token",
:headers
{"Content-Type" "application/x-www-form-urlencoded",
"Authorization"
"OAuth oauth_consumer_key=\"nTCqzIs2jIWSHZw2YNGmw\", oauth_nonce=\"KT-vrp_EqXfYnaCkSartQf3atjj9TK5TxqR44ap25bM\", oauth_signature=\"5Hljpn2TUSeJO4UWR6M8IpxVvuo%3D\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"1457741832\", oauth_version=\"1.0\""},
:throw-exceptions? false}
To send the request simply pass the request map to your favourite HTTP client.
(require '[clj-http.client])
(clj-http.client/request (one/request-token-request consumer))
(require '[aleph.http])
(aleph.http/request (one/request-token-request consumer))
The response you get back from the above request-token-request
will include an
oauth_token
you can use to ask the user for access to his or her account.
Let’s assume you used Aleph to send the request-token-request
request.
(def parse-form
(comp ring.util.codec/form-decode byte-streams/to-string))
(def request-token-response
@(manifold.deferred/chain
(aleph.http/request (one/request-token-request consumer))
#(update % :body parse-form)))
;; (:body request-token-response)
;; => {"oauth_token" "t89IVgAAAAAADf8pAAABU2gvEc8",
;; "oauth_token_secret" "XKkTgSNsLNNqRFyZPTf6W4OJT428JeL2",
;; "oauth_callback_confirmed" "true"}
(authorization-url
consumer
{"oauth_token" (get-in request-token-response [:body "oauth_token"])})
authorization-url
will return a string URL that you can send a user to in
order to kick of the user-facing part of the OAuth flow.
When the user surely decides to grant you access to his or her account the OAuth
provider sends the user to your :callback-uri
. Some providers allow you to
change this URL, others do not. Last time I check Twitter allowed you to opt in
or out of locking the :callback-uri
, which is great in production.
When redirected back you’ll receive a couple of GET
parameters containing two
important values needed to finally get your hands on an access token. The URL
will be of the following form:
http://example.com/oauth/callback?{oauth_token,oauth_verifier}
(If you’re not familiar with the above notation the curly braces indicate GET
parameters that will show up on the end of your URL.)
You’ll need an HTTP endpoint to capture the incoming request from your OAuth
provider, and you’ll need to parse the oauth_token
and oauth_verifier
. It’s
pretty straight forward to do so with ring.util.codec
:
(defn parse-verifier-url
[^String url]
(let [uri (java.net.URI. url)]
(ring.util.codec/form-decode (.getQuery uri))))
(parse-verifier-url
"http://127.0.0.1:3000/oauth/callback?oauth_token=abc123&oauth_verifier=cba321")
;; => {"oauth_token" "abc123" "oauth_verifier" "cba321"}
Keep hold of both the oauth_token
and oauth_verifier
because you need them
to get your hands on an access token.
Now we can send a request to get an access token! Hooray!
(access-token-request consumer {"oauth_token" "abc123"
"oauth_verifier" "bca321"})
The request will look something like this:
{:request-method :post,
:url "https://api.twitter.com/oauth/access_token",
:headers
{"Content-Type" "application/x-www-form-urlencoded",
"Authorization"
"OAuth oauth_consumer_key=\"nTCqzIs2jIWSHZw2YNGmw\", oauth_nonce=\"JJpnpVbOpteucb0LfHPMMZk0g2ehQkkFUM8AT3_oj4Q\", oauth_signature=\"e+tgaWSrN5Mzz5yKmNkhkhheQ6U%3D\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"1457743849\", oauth_token=\"F096MgAAAAAADf8pAAABU2fcrTM\", oauth_verifier=\"kk9MGzbHcIMnMMJxpecMak7OXvZTCdLo\", oauth_version=\"1.0\""}}
Again, to actually send the request you can use your favourite HTTP library.
The response from this last request will contain the actual oauth_token
and
oauth_token_secret
. These you’ll likely want to store in your database because
they’re the credentials you’ll use to masquerade as your new user.
Once you have your hands on both your application credentials, and a user’s token you can send requests on behalf of that user. These requests have to be cryptographically signed like any other so there’s a function provided to make this easier in your app.
(let [request {:request-method :get
:url "https://api.twitter.com/account/verify_credentials.json"
:query-params {"include_email" "true"
"skip_statuses" "true"}}
access-token {:token "access-token"
:secret "access-token-secret"}]
(one/sign-request consumer request access-token))
You can use the hash-map returned to make a request with your favourite HTTP client as before.