Skip to content

Commit

Permalink
Okay, ready for release I think. Docs fine-tuned, all three examples …
Browse files Browse the repository at this point in the history
…tested and tweaked. Tests all passing.
  • Loading branch information
ddellacosta committed Oct 24, 2012
1 parent c6eaf22 commit a0b0c04
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 42 deletions.
27 changes: 14 additions & 13 deletions README.md
Expand Up @@ -2,7 +2,7 @@

friend-oauth2 is an oauth2 workflow for Chas Emerick's [Friend][1] library.

[Examples][2] have been implemented for [app.net's OAuth2](https://github.com/appdotnet/api-spec/blob/master/auth.md) as well as [Facebook's server-side authentication](https://developers.facebook.com/docs/authentication/server-side/).
[Working examples][2] have been implemented for [app.net's OAuth2](https://github.com/appdotnet/api-spec/blob/master/auth.md), [Facebook's server-side authentication](https://developers.facebook.com/docs/authentication/server-side/), and [Github's OAuth2](http://developer.github.com/v3/oauth/).

## Installation

Expand All @@ -18,34 +18,35 @@ For now, the best reference is the [Friend-OAuth2 examples][2]. Also please refe

Check out the ring-app handlers in the examples for some examples of how authentication and authorization routes are set up per Friend's config.

### Writing your own handler.
### Configuring your handler.

See the one of the [example handlers][2] (appdotnet_handler.clj or facebook_handler.clj) for an example.
(See the one of the [example handlers][2] (appdotnet_handler.clj, facebook_handler.clj or github_handler.clj) for working examples.)

A brief description of the necessary configuration:

1. `client-config` holds the basic information which changes from app-to-app regardless of the provider: client-id, client-secret, and the applications callback url.

2. The `redirect-uri` map holds the provider-specific configuration for the initial redirect to the OAuth2 provider (the user-facing GET request).
2. The `authentication-uri` map holds the provider-specific configuration for the initial redirect to the OAuth2 provider (the user-facing GET request).

3. The `access-token-uri` map holds the provider-specific configuration for the access_token request, after the code is returned from the previous redirect (a server-to-server POST request).

4. `access-token-parsefn` is a provider-specific function which parses the body of the access_token response. This is necessary because different providers return different formats; for example, app.net returns the access_token in JSON format, whereas Facebook provides this as a query string.
4. `access-token-parsefn` is a provider-specific function which parses the access_token response and returns just the access_token. If your OAuth2 provider does not follow the RFC (http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-5.1) then you can pass in a custom function to parse the access-token response. See the [Facebook and Github examples][2] for reference.

5. `config-auth` ...incomplete, TODO.
5. `config-auth` ...TBD...

## Changelog 0.2.0
## Changelog 0.1.0 -> 0.2.0

* Added tests! Refactored!
* Made access-token-parsefn optional, as set up to follow spec ()
* tweaked naming scheme for config ()
* A helper function has been added (`format-config-uri`) to configure the redirect url in the config.
* :redirect-uri in the uri-config has been renamed to :authentication-uri, as it more closely matches the RFC (and it actually makes sense)
* The access-token-parsefn functionality has been tweaked. If the access-token is returned as defined in the spec (http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-5.1, as "application/json"), then it will automatically handle that. Otherwise you can still pass in the access-token-parsefn to override, and it will use that. See the [Facebook and Github examples][2] for reference. **Note that this function also now takes the entire response, rather than just the body.**

## TODO:
## To-do:

* Handle exceptions/errors after redirect and access_token request.
* Add a better authorization scheme (in terms of authorization and auth-map settings), preferably one which integrates Friend's credential-fn when the access_token is received.
* Add 'state' parameter by default in redirect/access_token parameters.
* Move client_id/client_secret to Authorization header (necessary? Good for security or immaterial? Does FB support this?)
* auth-map: should we be using the access-token as identity? Are there any downsides to this, especially in terms of security?
* Add 'state' parameter by default. (http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-10.12)
* Move client_id/client_secret to Authorization header (necessary? Good for security or immaterial? Does FB support this?) (http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-2.3)
* What's that thing I'm getting on the end of my url when I log in via FB ("#_=_")? Fix.

## License
Expand Down
47 changes: 23 additions & 24 deletions src/friend_oauth2/workflow.clj
Expand Up @@ -11,11 +11,11 @@
#(str %1 (get-in client-config [:callback %2]))
"" [:domain :path]))

(defn format-authorization-uri
"Formats the authorization uri"
[{:keys [authorization-uri]}]
(str (authorization-uri :url) "?"
(codec/form-encode (authorization-uri :query))))
(defn format-authentication-uri
"Formats the client authentication uri"
[{:keys [authentication-uri]}]
(str (authentication-uri :url) "?"
(codec/form-encode (authentication-uri :query))))

(defn replace-authorization-code
"Formats the token uri with the authorization code"
Expand All @@ -38,46 +38,45 @@
(j/parse-string (response :body)))
:access_token))

(defn make-auth
"Creates the auth-map for Friend"
[identity]
(with-meta identity
{:type ::friend/auth
::friend/workflow :email-login
::friend/redirect-on-auth? true}))

(defn workflow
"Workflow for OAuth2"
[config]
(fn [request]
;; If we have a callback for this workflow
;; or a login URL in the request, process it.
(if (or (= (:uri request)
(:path (:callback (:client-config config))))
(= (:uri request)
(or (config :login-uri) "/login")))

;; Step 2, 3:
;; accept callback and get access_token (via POST)
;; Steps 2 and 3:
;; accept auth code callback, get access_token (via POST)
(if-let [code (extract-code request)]
(let [access-token-uri ((config :uri-config) :access-token-uri)
token-url (assoc-in access-token-uri [:query]
(replace-authorization-code access-token-uri code))

;; Step 4:
;; access_token response. Custom function for handling
;; response body is passwed in via the :access-token-parsefn
;; response body is pass in via the :access-token-parsefn
access-token ((or (config :access-token-parsefn)
extract-access-token)
(client/post
(:url token-url)
{:form-params (:query token-url)}))]

;; Auth Map, as expected by Friend on a successful authentication:
(vary-meta
;; Identity map
(merge
;; At the least we will have the access-token,
;; so we will use that for the identity (for now).
{:identity access-token
:access_token access-token}
(:config-auth config)) ;; config-auth is provider specific auth settings
;; Meta-data
merge
{:type ::friend/auth}
{::friend/workflow :oauth2
::friend/redirect-on-auth? true}))
;; The auth map for a successful authentication:
(make-auth (merge {:identity access-token
:access_token access-token}
(:config-auth config))))

;; Step 1: redirect to OAuth2 provider. Code will be in response.
(ring.util.response/redirect
(format-authorization-uri (config :uri-config)))))))
(format-authentication-uri (config :uri-config)))))))
21 changes: 16 additions & 5 deletions test/friend_oauth2/workflow_facts.clj
Expand Up @@ -19,9 +19,9 @@
:callback {:domain "http://127.0.0.1" :path "/redirect"}})

(def uri-config-fixture
{:authorization-uri {:url "http://example.com"
:query {:client_id (:client-id client-config-fixture)
:redirect_uri (friend-oauth2/format-config-uri client-config-fixture)}}
{:authentication-uri {:url "http://example.com"
:query {:client_id (:client-id client-config-fixture)
:redirect_uri (friend-oauth2/format-config-uri client-config-fixture)}}

:access-token-uri {:url "http://example.com"
:query {:client_id (client-config-fixture :client-id)
Expand All @@ -35,6 +35,8 @@
:uri-config uri-config-fixture})
request-or-response))

(def identity-fixture
{:identity "my-access-token", :access_token "my-access-token"})

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
Expand Down Expand Up @@ -116,15 +118,24 @@
=> "http://127.0.0.1/redirect")

(fact
"Formats the redirect uri"
(friend-oauth2/format-authorization-uri uri-config-fixture)
"Formats the client authentication uri"
(friend-oauth2/format-authentication-uri uri-config-fixture)
=> "http://example.com?client_id=my-client-id&redirect_uri=http%3A%2F%2F127.0.0.1%2Fredirect")

(fact
"Replaces the authorization code"
((friend-oauth2/replace-authorization-code (uri-config-fixture :access-token-uri) "my-code") :code)
=> "my-code")

(fact
"Creates the auth-map for Friend with proper meta-data"
(meta (friend-oauth2/make-auth identity-fixture))
=>
{:type ::friend/auth
::friend/workflow :email-login
::friend/redirect-on-auth? true})



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
Expand Down

0 comments on commit a0b0c04

Please sign in to comment.