In project.clj:
[clojusc/friend-oauth2 "0.2.0"]
As a security best practice, it is strongly recommended that you use Environ or similar to store your credentials in environment variables, rather than hardcode any credentials into the source code itself. In this way, if an attacker steals your code, they still won't be able to use your credentials.
To use Environ, also add:
[environ "0.5.0"]
to your project.clj.
To be able to use friend-oauth2, you must obtain site credentials from
the third party providers you wish to authenticte against, then
provide those credentials to friend-oauth2. The exact procedure for
obtaining the credentials varies by provider; consult their docs for
details. You will be given a :client-id
and a
:client-secret
. (These are for your server code, not for your
clients - you are a client of the third party provdier.)
Besides friend-oauth2, you must also set up Friend itself. This is typically done near where your main Ring app stack is being constructed. friend-oauth2 is then given to Friend as a possible workflow. It's easy to use other workflows besides friend-oauth2 in the same app, too. See the [Friend docs][1] for more information on setting up Friend itself.
Near where your Ring stack is being constructed, add the following requires:
(ns your.ns.here
(:require [cemerick.friend :as friend]
[friend-oauth2.workflow :as oauth2]
[friend-oauth2.util :refer [format-config-uri]]
[environ.core :refer [env]])
Using Environ and Google APIs OAuth2 as an example:
(def client-config
{:client-id (env :friend-oauth2-client-id)
:client-secret (env :friend-oauth2-client-secret)
:callback {:domain "http://localhost:8090" ;; replace this for production with the appropriate site URL
:path "/oauth2callback"}})
In ~/.lein/profiles.clj
, add your actual credentials (N.B. this file
is not checked into source control! It should only be present on
your local machine, and should be protected from other users as it
contains sensitive passwords):
{:user {:env {
:friend-oauth2-client-id "12345.apps.googleusercontent.com"
:friend-oauth2-client-secret "xyz"
}}
In production, these can be set at the ops level as ordinary environment variables (with underscores instead of dashes):
export FRIEND_OAUTH2_CLIENT_ID=12345.apps.googleusercontent.com
export FRIEND_OAUTH2_CLIENT_secret=xyz
./bin/run-my-app.sh
If the user login succeeds on the third party site, you will get a
token back in your credential-fn
uniquely identifying the user. You
can use this token to look up user metadata from the third party
service. What metadata you can look up is provider-specific. You can
also use it to create accounts in your own system, assign roles to the
user, and so on, as your particular app requires..
(defn credential-fn
"Upon successful authentication with the third party, Friend calls
this function with the user's token. This function is responsible for
translating that into a Friend identity map with at least the :identity
and :roles keys. How you decide what roles to grant users is up to you;
you could e.g. look them up in a database.
You can also return nil here if you decide that the token provided
is invalid. This could be used to implement e.g. banning users.
This example code just automatically assigns anyone who has
authenticated with the third party the nominal role of ::user."
[token]
{:identity token
:roles #{::user}})
credential-fn
behaves slightly differently differently in
friend-oauth2 than in other workflows: it allows you to intercept the
access token at the end of the 3rd-party authentication process and
inject your own functionality. This is an artifact of OAuth2's
original purpose as a protocol for obtaining 3rd-party
authorization of third party resources, not authentication of
user identity as such. It therefore does not return the user metadata
you probably find interesting; just a token. However, nowadays almost
everyone uses OAuth primarily for authentication rather than
authorization, so it is typical to find e.g. local database
interaction in your own system, or third party metadata lookup here.
Create the following data structure, given with Google as the example. Consult the examples and the third party's documentation for the URLs to use with other providers.
(def uri-config
{:authentication-uri {:url "https://accounts.google.com/o/oauth2/auth"
:query {:client_id (:client-id client-config)
:response_type "code"
:redirect_uri (format-config-uri client-config)
:scope "email"}}
:access-token-uri {:url "https://accounts.google.com/o/oauth2/token"
:query {:client_id (:client-id client-config)
:client_secret (:client-secret client-config)
:grant_type "authorization_code"
:redirect_uri (format-config-uri client-config)}}})
Finally, create an OAuth2 workflow and add it to your Friend handler, then add the Friend handler to your Ring stack:
(def friend-config
{:allow-anon? true
:workflows [(oauth2/workflow
{:client-config client-config
:uri-config uri-config
:credential-fn credential-fn})
;; Optionally add other workflows here...
]})
(def app
(-> my-ring-app
(friend/authenticate friend-config)
handler/site))
Access control is performed by Friend as usual. See [Friend's docs][1] for the full details.
For example, to allow access to a certain Compojure route only to users who
are logged in and have the ::oauth2-user
role:
(GET "/authlink" request
(friend/authorize #{::oauth2-user} "Authorized page."))
Or, you can protect an entire Ring substack with Friend's wrap-authorize
function:
(friend/wrap-authorize admin-routes #{::administrator})
Check out the [friend-oauth2 examples][2] and refer to the [Friend README][1] for more information.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close