{"id":20060930,"url":"https://github.com/jcf/oauth-two","last_synced_at":"2025-05-05T15:33:31.967Z","repository":{"id":57713889,"uuid":"56058790","full_name":"jcf/oauth-two","owner":"jcf","description":"OAuth 2.0 client in Clojure","archived":false,"fork":false,"pushed_at":"2016-05-04T11:55:25.000Z","size":24,"stargazers_count":19,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-12T23:37:32.896Z","etag":null,"topics":["authentication","clojure","oauth2","oauth2-client"],"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:57:10.000Z","updated_at":"2024-09-24T15:06:18.000Z","dependencies_parsed_at":"2022-08-25T10:31:06.203Z","dependency_job_id":null,"html_url":"https://github.com/jcf/oauth-two","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcf%2Foauth-two","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcf%2Foauth-two/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcf%2Foauth-two/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcf%2Foauth-two/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jcf","download_url":"https://codeload.github.com/jcf/oauth-two/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224452792,"owners_count":17313677,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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","oauth2","oauth2-client"],"created_at":"2024-11-13T13:17:34.193Z","updated_at":"2024-11-13T13:17:35.601Z","avatar_url":"https://github.com/jcf.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"#+TITLE: OAuth Two\n#+STARTUP: overview\n\n#+BEGIN_HTML\n\u003ca href=\"https://circleci.com/gh/jcf/oauth-two\"\u003e\n  \u003cimg src=\"https://circleci.com/gh/jcf/oauth-two.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.two\"\u003e\n  \u003cimg src=\"https://img.shields.io/clojars/v/oauth/oauth.two.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.two :as two])\n#+end_src\n\nCreate a client using the credentials provided by (in this example) Vimeo. The\nclient holds on to important URLs, and tokens. We'll pull our client ID and\nsecret from environment variablesa to avoid adding sensitive credentials to our\nrepository.\n\n#+begin_src clojure\n  (def client\n    (two/make-client\n     {:access-uri \"https://api.vimeo.com/oauth/access_token\"\n      :authorize-uri \"https://api.vimeo.com/oauth/authorize\"\n      :id (System/getenv \"VIMEO_CLIENT_ID\")\n      :secret (System/getenv \"VIMEO_CLIENT_SECRET\")}))\n#+end_src\n\nThe ~:access-uri~ is where we get access tokens, and the `:authorize-uri` is\nwhere we redirect users to show them the provider's site.\n\n** Generate provider-specific redirect URL\nTo kick off the OAuth dance we need to create a URL to send the user to. This\nURL is owned by the OAuth provider, and it's where the provider asks the user if\nthey want to grant us access.\n\nTo generate the authorisation URL we use the ~authorization-url~ function like\nso:\n\n#+begin_src clojure\n  (two/authorization-url client)\n#+end_src\n\nWe can pass in additional parameters to include in the OAuth authorisation URL\nby providing an optional map as the second argument.\n\n#+begin_src clojure\n  (two/authorization-url client {:state \"hello world\"})\n#+end_src\n\nA third optional argument allows you to pass additional query parameters, like\n[[https://developers.google.com/identity/protocols/OAuth2WebServer][Google's \"prompt\" parameter]].\n\n#+begin_src clojure\n  (two/authorization-url client\n                         {:state \"hello world\"}\n                         {:prompt \"select_account\"})\n#+end_src\n\n** Handle response from provider\nWhen the user decides to accept our request to access his or her account we\nreceive a ~GET~ request by virtue of the provider redirecting the user to us.\n\nQuery parameters are appended to our ~:callback-uri~ that inform us if the\nauthorisation request was successful or not.\n\n*** Success\nThis redirect includes an authorisation code, and any local state provided\npreviously.\n\n| Field   | Required | Description                                |\n|---------+----------+--------------------------------------------|\n| ~code~  | REQUIRED | The auth code generated by the Auth server |\n| ~state~ | REQUIRED | Whatever value we passed previously        |\n\n#+begin_quote\nThe authorization code generated by the authorization server. The authorization\ncode MUST expire shortly after it is issued to mitigate the risk of leaks. A\nmaximum authorization code lifetime of 10 minutes is RECOMMENDED. The client\nMUST NOT use the authorization code more than once. If an authorization code is\nused more than once, the authorization server MUST deny the request and SHOULD\nrevoke (when possible) all tokens previously issued based on that authorization\ncode. The authorization code is bound to the client identifier and redirection\nURI.\n#+end_quote\n\n*** Failure\nIf something goes wrong the Auth server redirects back to the client with the\nfollowing parameters:\n\n| Field               | Required | Description                               |\n|---------------------+----------+-------------------------------------------|\n| ~error~             | REQUIRED | A single ASCII error code                 |\n| ~error_description~ | OPTIONAL | Human-readable ASCII error description    |\n| ~error_uri~         | OPTIONAL | A URI with more human-readable error info |\n| ~state~             | REQUIRED | Whatever value we passed previously       |\n\nError codes are as follows:\n\n| Error                       | Description                                                                                                                                                                                                                                                            |\n|-----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| ~unauthorized_client~       | The client is not authorized to request an authorization code using this method.                                                                                                                                                                                       |\n| ~access_denied~             | The resource owner or authorization server denied the request.                                                                                                                                                                                                         |\n| ~unsupported_response_type~ | The authorization server does not support obtaining an authorization code using this method.                                                                                                                                                                           |\n| ~invalid_scope~             | The requested scope is invalid, unknown, or malformed.                                                                                                                                                                                                                 |\n| ~server_error~              | The authorization server encountered an unexpected condition that prevented it from fulfilling the request. (This error code is needed because a 500 Internal Server Error HTTP status code cannot be returned to the client via an HTTP redirect.)                    |\n| ~temporarily_unavailable~   | The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.  (This error code is needed because a 503 Service Unavailable HTTP status code cannot be returned to the client via an HTTP redirect.) |\n\n#+begin_src http\n  HTTP/1.1 302 Found\n  Location: https://client.example.com/cb?error=access_denied\u0026state=xyz\n#+end_src\n\n** Request access token\nWith the ~code~ from the provider we can generate a request map for getting our\naccess token via the ~access-token-request~.\n\n#+begin_src clojure\n  (two/access-token-request\n   (make-client {:access-uri \"http://example.com/oauth/access-token\"\n                 :id \"id\"\n                 :secret \"secret\"})\n   {:code \"abc\"})\n#+end_src\n\nThis will produce a request map with ~Basic~ authentication via the client's ID\nand secret in addition to the ~code~.\n\n#+begin_src clojure\n  {:request-method :post,\n   :url \"http://example.com/oauth/access-token\",\n   :headers\n   {\"authorization\" \"Basic aWQ6c2VjcmV0\",\n    \"content-type\" \"application/x-www-form-urlencoded\"},\n   :body \"client_id=id\u0026code=abc\u0026grant_type=authorization_code\"}\n#+end_src\n\nYou can then issue this request using your favourite HTTP client, with any error\nhandling, JSON response parsing, metrics etc.\n\nAll OAuth 2.0 providers will return a custom response to the access token\nrequest. The spec provides the following JSON as an example response:\n\n#+begin_src json\n  {\n    \"access_token\": \"2YotnFZFEjr1zCsicMWpAA\",\n    \"token_type\": \"example\",\n    \"expires_in\": 3600,\n    \"refresh_token\": \"tGzv3JOkF0XG5Qx2TlKWIA\",\n    \"example_parameter\": \"example_value\"\n  }\n#+end_src\n\nhttps://tools.ietf.org/html/rfc6749#section-4.1.4\n\nThe spec goes on to define how these attributes should be used in other flows.\n\n#+begin_example\n  access_token\n        REQUIRED.  The access token issued by the authorization server.\n\n  token_type\n        REQUIRED.  The type of the token issued as described in\n        Section 7.1.  Value is case insensitive.\n\n  expires_in\n        RECOMMENDED.  The lifetime in seconds of the access token.  For\n        example, the value \"3600\" denotes that the access token will\n        expire in one hour from the time the response was generated.\n        If omitted, the authorization server SHOULD provide the\n        expiration time via other means or document the default value.\n\n  scope\n        OPTIONAL, if identical to the scope requested by the client;\n        otherwise, REQUIRED.  The scope of the access token as\n        described by Section 3.3.\n\n  state\n        REQUIRED if the \"state\" parameter was present in the client\n        authorization request.  The exact value received from the\n        client.\n#+end_example\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcf%2Foauth-two","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjcf%2Foauth-two","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcf%2Foauth-two/lists"}