Created
March 25, 2015 20:16
-
-
Save jwhitlark/5dc423485548c31d2c72 to your computer and use it in GitHub Desktop.
Combine friend, oauth2, and JWT token processing.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns foo.auth.google | |
(:require [clojure.string :as str] | |
[clojure.java.io :as io] | |
[clojure.data.json :as json] | |
[clojure.logging :refer :all] | |
[friend-oauth2.util :refer [format-config-uri]] | |
[friend-oauth2.workflow :as oauth2]) | |
(:import [java.security.cert CertificateFactory] | |
[org.apache.commons.codec.binary Base64])) | |
(def client-config | |
{:client-id <client-id> | |
:client-secret <client-secret> | |
:callback {:domain "http://localhost:8090" :path "/oauth2callback"}}) | |
(defn credential-fn [token] | |
(debug "in oauth cred-fn: " token) | |
(let [username (get-in token [:access-token :email]) | |
bare-token (get-in token [:access-token :token]) | |
expires (get-in token [:expires])] | |
(info "User authenticated:" username ) | |
{:identity bare-token | |
:username username | |
:expires expires | |
:roles #{::user}})) | |
(let [cert-factory (CertificateFactory/getInstance "X.509") | |
gen-cert #(.generateCertificates cert-factory %)] | |
(defn load-x509-cert "Load an X.509 cert from a string." | |
[s] | |
(->> s | |
.getBytes | |
io/input-stream | |
gen-cert | |
first))) | |
(defn load-certs "Load googles certs, put them in the format we need, | |
and map them to their KIDs" | |
[] | |
(let [raw-certs (-> "https://www.googleapis.com/oauth2/v1/certs" slurp json/read-str)] | |
(zipmap (keys raw-certs) (mapv load-x509-cert (vals raw-certs))))) | |
(defn- rsa-verify | |
"Function to verify data and signature with RSA algorithm." | |
[alg key body signature & {:keys [charset] :or {charset "UTF-8"}}] | |
(let [sig (doto (java.security.Signature/getInstance alg) | |
(.initVerify key) | |
(.update (.getBytes body charset)))] | |
(.verify sig signature))) | |
(defn- decode-piece [piece] | |
(->> (Base64/decodeBase64 piece) | |
(map char) | |
(apply str) | |
(#(json/read-str % :key-fn keyword)))) | |
(defn parse-jwt [jwt-str] | |
(let [[header, claims, sig] (str/split jwt-str #"\." )] | |
{:header (decode-piece header) | |
:claims (decode-piece claims) | |
:sig (Base64/decodeBase64 sig) | |
:payload (str/join "." [header, claims])})) | |
(defn valid-signature? | |
"Validates a JWT against its signature, downloading the google certs | |
EACH TIME." | |
([jwt-map] (valid-signature? jwt-map (load-certs))) | |
([jwt-map certs] | |
(let [kid (get-in jwt-map [:header :kid]) | |
alg (get-in jwt-map [:headers :alg]) | |
cert (get certs kid)] | |
(if (nil? cert) | |
(throw (Exception. "No matching cert."))) | |
(rsa-verify alg cert (:payload jwt-map) (:sig jwt-map))))) | |
(defn process-jwt [{body :body}] | |
(let [resp (json/read-str body :key-fn keyword) | |
access-token (:access_token resp) | |
jwt-map (parse-jwt (:id_token resp)) | |
validated? (valid-signature? jwt-map) | |
token {:token access-token | |
:validated? validated? | |
:email (get-in jwt-map [:claims :email]) | |
:expires (get-in jwt-map [:claims :exp])}] | |
(debug "token: " token) | |
token)) | |
(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) | |
:hd <your-domain> | |
:scope "openid email profile"}} | |
: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)}}}) | |
(defn workflow [] | |
(oauth2/workflow | |
{:client-config client-config | |
:uri-config uri-config | |
:credential-fn credential-fn | |
:access-token-parsefn process-jwt | |
})) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment