I'd like to get OpenID connect working in my little luminus project. I'm a little new to the workflow in luminus/ring/compojure (coming from django, flask, and servlets mostly). I've successfully redirected to Google so I get the "code" back from Google, but then I need to make one more request to Google before logging in the user and this call requires another callback the user is not involved in, so I need to put the user's request on hold like a promise, but I'm not sure how that part works in compojure.

; this is my code that redirects them to Google, where they accept
(defn login [params] 
  (let [google-oauth2-client-id (System/getenv "GOOGLE_OAUTH2_CLIENT_ID")
        base-url "https://accounts.google.com/o/oauth2/auth"
        args {"client_id" google-oauth2-client-id
              "response_type" "code"
              "scope" "openid email"
              "redirect_uri" "http://localhost:3000/oauth2Callback"
              "state" "anti-forgery here"}]

    (assert google-oauth2-client-id "can't find GOOGLE_OAUTH2_CLIENT_ID in environment")

    (redirect (str base-url "?" (make-query-string args)))
    )
)

; this is my code handling Google's first response
(defn oauth2-callback [params]
  ; params has the code to send to Google

  ; here I should send another request to google that comes back to another callback like oauth2-token-callback that processes the request to the user in the current context

  (redirect "/youreloggedin")
)

By the end of this method I should be sending the user a message saying they're logged in, but I need to wait until the request comes back. How is this workflow handled in luminus?


Solved. I didn't realize I could just ignore the callback parameter.

  (client/post "https://www.googleapis.com/oauth2/v3/token"
               {:headers {"X-Api-Version" "2"}
                :content-type :application/x-www-form-urlencoded
                :form-params {:code (params :code)
                              :client_id (System/getenv "GOOGLE_OAUTH2_CLIENT_ID")
                              :client_secret (System/getenv "GOOGLE_OAUTH2_CLIENT_SECRET")
                              :redirect_uri "http://localhost:3000/oauth2Callback" ; ignored
                              :grant_type "authorization_code"
                              }
                :as :auto ; decode the body straight to hash (if possible)
            })
1

There are 1 answers

0
juan.facorro On BEST ANSWER

Based on the documentation for Google's OAuth2 for web servers here, the flow consists of the following steps:

  1. Your application redirects a browser to a Google URL; the URL includes query parameters that indicate the type of access being requested.
  2. The result is an authorization code, which Google returns to your application in a query string.
  3. After receiving the authorization code, your application can exchange the code (along with a client ID and client secret) for an access token and, in some cases, a refresh token.

If I understood your question correctly, step 3 does not necessarily involve a callback to your server, you can just perform the request to Google with an HTTP client. I recently implemented OAuth2 for GitHub in this project, step 3 is implemented in this function:

(defn callback
  "Handles the callback from GitHub OAuth flow."
  [code]
  (let [params {:form-params {:client_id client-id
                              :client_secret client-secret
                              :code code}}
        {:keys [body]} (client/post access-token-url params) ;; This is doing the POST
                                                             ;; request to GitHub.
        token  ((qs-map body) "access_token")]               ;; Getting the token from 
                                                             ;; the response here. 
    {:status 302
     :headers {"location" "/repos"
               "set-cookie" (str "token=" token ";Path=/")}}))

I used clj-http as the HTTP client but any other will do.