{"id":20060943,"url":"https://github.com/jcf/oauth-one","last_synced_at":"2026-03-10T05:02:14.157Z","repository":{"id":62433872,"uuid":"56058755","full_name":"jcf/oauth-one","owner":"jcf","description":"OAuth 1.0 client in Clojure","archived":false,"fork":false,"pushed_at":"2016-12-06T19:19:12.000Z","size":41,"stargazers_count":5,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-05T15:51:20.477Z","etag":null,"topics":["authentication","clojure","oauth","oauth-client","twitter"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jcf.png","metadata":{"files":{"readme":"README.org","changelog":"CHANGELOG.org","contributing":".github/CONTRIBUTING.org","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-04-12T11:56:45.000Z","updated_at":"2024-11-06T19:05:23.000Z","dependencies_parsed_at":"2022-11-01T21:01:47.401Z","dependency_job_id":null,"html_url":"https://github.com/jcf/oauth-one","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/jcf/oauth-one","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcf%2Foauth-one","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcf%2Foauth-one/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcf%2Foauth-one/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcf%2Foauth-one/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jcf","download_url":"https://codeload.github.com/jcf/oauth-one/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcf%2Foauth-one/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30325598,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T01:36:58.598Z","status":"online","status_checked_at":"2026-03-10T02:00:06.579Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","clojure","oauth","oauth-client","twitter"],"created_at":"2024-11-13T13:17:36.694Z","updated_at":"2026-03-10T05:02:14.134Z","avatar_url":"https://github.com/jcf.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"#+TITLE: OAuth One\n\n#+BEGIN_HTML\n\u003ca href=\"https://circleci.com/gh/jcf/oauth-one\"\u003e\n  \u003cimg src=\"https://circleci.com/gh/jcf/oauth-one.svg\"\u003e\u003c/img\u003e\n\u003c/a\u003e\n#+END_HTML\n\n* Installation\nThis project is under active development, and has yet to reach 1.0. As such the\nAPI may change.\n\n#+BEGIN_HTML\n  \u003ca href=\"https://clojars.org/oauth/oauth.one\"\u003e\n    \u003cimg src=\"https://img.shields.io/clojars/v/oauth/oauth.one.svg\"\u003e\u003c/img\u003e\n  \u003c/a\u003e\n#+END_HTML\n\n* Getting started\nRequire the library with a convenient alias that we can make use of later.\n\n#+begin_src clojure\n  (require '[oauth.one :as one])\n#+end_src\n\nCreate a consumer using the credentials provided by (in this example) Twitter.\nThe consumer holds on to important URLs, tokens and information about how to\ngenerate OAuth requests. We'll pull our consumer key and secret from environment\nvariables to avoid adding sensitive credentials to our repository.\n\n#+begin_src clojure\n  (def consumer\n    (one/make-consumer\n     {:access-uri \"https://api.twitter.com/oauth/access_token\"\n      :authorize-uri \"https://api.twitter.com/oauth/authorize\"\n      :callback-uri \"http://127.0.0.1:3000/oauth/callback\"\n      :key (System/getenv \"TWITTER_CONSUMER_KEY\")\n      :request-uri \"https://api.twitter.com/oauth/request_token\"\n      :secret (System/getenv \"TWITTER_CONSUMER_SECRET\")\n      :signature-algo :hmac-sha1}))\n#+end_src\n\n** Request token\nThe ~:callback-uri~ is a local address we can use for testing purposes. All of\nthe above can and should be pulled from whatever configuration system you're\nusing your application (I'd recommend a combination of [[https://github.com/weavejester/environ][environ]] and [[https://github.com/plumatic/schema][Schema]]).\nCheck out my [[https://github.com/jcf/lein-template][lein-template]] for an example of how to pull your configuration from\nenvironment variables and both validate and coerce the data.\n\nNow that you have a consumer you can build a request to request a token.\n\n#+begin_src clojure\n  (one/request-token-request consumer)\n#+end_src\n\n~request-token-request~ returns a ~clj-http~ compatible hash-map that can be\npassed to your favourite HTTP library quite easily. An example request map looks\nsomething like this:\n\n#+begin_src clojure\n  {:request-method :post,\n   :url \"https://api.twitter.com/oauth/request_token\",\n   :headers\n   {\"Content-Type\" \"application/x-www-form-urlencoded\",\n    \"Authorization\"\n    \"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\\\"\"},\n   :throw-exceptions? false}\n#+end_src\n\nTo send the request simply pass the request map to your favourite HTTP client.\n\n#+begin_src clojure\n  (require '[clj-http.client])\n  (clj-http.client/request (one/request-token-request consumer))\n\n  (require '[aleph.http])\n  (aleph.http/request (one/request-token-request consumer))\n#+end_src\n\n** Authorisation URL\nThe response you get back from the above ~request-token-request~ will include an\n~oauth_token~ you can use to ask the user for access to his or her account.\n\nLet's assume you used Aleph to send the ~request-token-request~ request.\n\n#+begin_src clojure\n  (def parse-form\n    (comp ring.util.codec/form-decode byte-streams/to-string))\n\n  (def request-token-response\n    @(manifold.deferred/chain\n      (aleph.http/request (one/request-token-request consumer))\n      #(update % :body parse-form)))\n\n  ;; (:body request-token-response)\n  ;; =\u003e {\"oauth_token\" \"t89IVgAAAAAADf8pAAABU2gvEc8\",\n  ;;     \"oauth_token_secret\" \"XKkTgSNsLNNqRFyZPTf6W4OJT428JeL2\",\n  ;;     \"oauth_callback_confirmed\" \"true\"}\n\n  (authorization-url\n   consumer\n   {\"oauth_token\" (get-in request-token-response [:body \"oauth_token\"])})\n#+end_src\n\n~authorization-url~ will return a string URL that you can send a user to in\norder to kick of the user-facing part of the OAuth flow.\n\n[[https://dl.dropboxusercontent.com/u/508427/imgs/twitter-oauth-flow-example.png]]\n\n** Access token\nWhen the user surely decides to grant you access to his or her account the OAuth\nprovider sends the user to your ~:callback-uri~. Some providers allow you to\nchange this URL, others do not. Last time I check Twitter allowed you to opt in\nor out of locking the ~:callback-uri~, which is great in production.\n\nWhen redirected back you'll receive a couple of ~GET~ parameters containing two\nimportant values needed to finally get your hands on an access token. The URL\nwill be of the following form:\n\nhttp://example.com/oauth/callback?{oauth_token,oauth_verifier}\n\n(If you're not familiar with the above notation the curly braces indicate ~GET~\nparameters that will show up on the end of your URL.)\n\nYou'll need an HTTP endpoint to capture the incoming request from your OAuth\nprovider, and you'll need to parse the ~oauth_token~ and ~oauth_verifier~. It's\npretty straight forward to do so with ~ring.util.codec~:\n\n#+begin_src clojure\n  (defn parse-verifier-url\n    [^String url]\n    (let [uri (java.net.URI. url)]\n      (ring.util.codec/form-decode (.getQuery uri))))\n\n  (parse-verifier-url\n   \"http://127.0.0.1:3000/oauth/callback?oauth_token=abc123\u0026oauth_verifier=cba321\")\n  ;; =\u003e {\"oauth_token\" \"abc123\" \"oauth_verifier\" \"cba321\"}\n#+end_src\n\nKeep hold of both the ~oauth_token~ and ~oauth_verifier~ because you need them\nto get your hands on an access token.\n\nNow we can send a request to get an access token! Hooray!\n\n#+begin_src clojure\n  (access-token-request consumer {\"oauth_token\" \"abc123\"\n                                  \"oauth_verifier\" \"bca321\"})\n#+end_src\n\nThe request will look something like this:\n\n#+begin_src clojure\n  {:request-method :post,\n   :url \"https://api.twitter.com/oauth/access_token\",\n   :headers\n   {\"Content-Type\" \"application/x-www-form-urlencoded\",\n    \"Authorization\"\n    \"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\\\"\"}}\n#+end_src\n\nAgain, to actually send the request you can use your favourite HTTP library.\n\nThe response from this last request will contain the actual ~oauth_token~ and\n~oauth_token_secret~. These you'll likely want to store in your database because\nthey're the credentials you'll use to masquerade as your new user.\n\n** Signing requests\nOnce you have your hands on both your application credentials, and a user's\ntoken you can send requests on behalf of that user. These requests have to be\ncryptographically signed like any other so there's a function provided to make\nthis easier in your app.\n\n#+begin_src clojure\n  (let [request {:request-method :get\n                 :url \"https://api.twitter.com/account/verify_credentials.json\"\n                 :query-params {\"include_email\" \"true\"\n                                \"skip_statuses\" \"true\"}}\n        access-token {:token \"access-token\"\n                      :secret \"access-token-secret\"}]\n    (one/sign-request consumer request access-token))\n#+end_src\n\nYou can use the hash-map returned to make a request with your favourite HTTP\nclient as before.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcf%2Foauth-one","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjcf%2Foauth-one","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcf%2Foauth-one/lists"}