Skip to content

Instantly share code, notes, and snippets.

@Jah524
Last active February 11, 2018 09:32
Show Gist options
  • Save Jah524/24dc6aedf50f5874afae7b6fa138ffe0 to your computer and use it in GitHub Desktop.
Save Jah524/24dc6aedf50f5874afae7b6fa138ffe0 to your computer and use it in GitHub Desktop.
Clojure: Sign up / Sign in with Google

Set up

  1. Build project at Developer Console
  2. Get client-id and client-secret from project you build and set up redirect-url (see project.clj)
  3. Implement OAuth flow (see following work flow and google.clj

Work flow

  1. Goto http://127.0.0.1:3449/auth/google on your browser (use your host and port).
  2. Your application redirects the user to Google.
  3. User grants permissions.
  4. Automatially call back to your application http://127.0.0.1:3449/auth/google-callback.
  5. Exchange authorization code for id_token
  6. You can get user's information from the token.
{
	"issuer" : "accounts.google.com",
	"issued_to" : "CLIENT-ID",
	"audience" : "CLIENT-ID",
	"user_id" : "xxx",
	"expires_in" : 3599,
	"issued_at" : yyy,
	"email" : "xxx@gmail.com",
	"email_verified" : true
}

Now you can identify an user by user_id

(ns auth.google
(:require
[clojure.data.json :as json]
[compojure.core :refer [defroutes GET]]
[config.core :refer [env]]
[ring.util.response :refer [redirect]]
[clj-http.client :as client]))
(defn rand-url-str [len]
(let [chars (concat (range 48 58) (range 97 123))]
(apply str (repeatedly len #(char (rand-nth chars))))))
(def google-base-uri "https://accounts.google.com")
(defn build-scopes
[scopes]
(->> scopes
(interpose "%20")
(apply str)))
(def scopes
["email" "profile"])
(defn google-request-code
[]
(let [{:keys [client-id client-secret callback]} (env :google)
state (rand-url-str 32)]
{:uri (str google-base-uri "/o/oauth2/auth?"
"client_id=" client-id "&"
"response_type=code&"
"scope=" (build-scopes scopes) "&"
"redirect_uri=" callback "&"
"state=" state)
:state state}))
(defn exchange-token
[code]
(let [{:keys [client-id client-secret callback]} (env :google)]
(-> (client/post (str google-base-uri "/o/oauth2/token")
{:form-params {"code" code
"client_id" client-id
"client_secret" client-secret
"redirect_uri" callback
"grant_type" "authorization_code"}})
:body
json/read-str)))
(defn token-info
[id-token]
(-> (client/get "https://www.googleapis.com/oauth2/v1/tokeninfo"
{:query-params {"id_token" id-token}})
:body
json/read-str))
(defroutes auth-google-routes
(GET "/google" req
(let [{:keys [session]} req
{:keys [uri state]} (google-request-code)]
(-> (redirect uri)
(assoc :session (assoc session :state state)))))
(GET "/google-callback" {params :params session :session}
(let [{:keys [code state]} params
session-state (:state session)]
(when (and state (= session-state state)); check CSRF
(let [{:strs [expires_in id_token]} (exchange-token code)]
(when (and id_token (> expires_in 0))
(let [api-result (token-info id_token)
{:strs [issuer issued_to audience user_id]} api-result]
(when (and (= issuer "accounts.google.com")
(= issued_to audience (:client-id (env :google))))
(if "registered?"
"login"
"register"))))))))))
...
{:dev {:env {:google {:client-id "XXX"
:client-secret "YYY"
:callback "http://127.0.0.1:3449/auth/google-callback"}}}}
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment