0 notes &
Login with Twitter clojure/java, this is a little tricky…
Last time I implemented the login with facebook and I was guessing that login with twitter was about the same, just change a couple of url, some secret key and some appid and I thought I would have been set.
Well I was wrong.
The basic idea is about the same, but it is implemented in a different way, facebook is way more straight and I would say easy or at least less complex, but it is just my idea.
Before to show the code I need to do a full disclaimer, I am not sure I am doing it right, it works but there might be better way to do the same, if so just let me know.
Just going straight to the code (how can I show gits on tumblr ???)
(ns morgan.models.social (:require [clj-http.client :as client]) (:import [org.scribe.oauth OAuthService] [org.scribe.builder ServiceBuilder] [org.scribe.model Token Verifier OAuthRequest Verb]) (:import [org.scribe.builder.api FacebookApi TwitterApi$Authenticate])) (def twitter-service (-> (doto (ServiceBuilder.) (.provider (TwitterApi$Authenticate.)) (.apiKey "Pqc8RQMeJwVKpv0vPqPg") (.apiSecret "4UvfkrZbmT4wmwhKrddsqZqIwPQRQ7X4akQW2zCLM") (.callback "https://morgan-siscia.rhcloud.com/tw")) (.build))) (def tokens (atom {})) (defn get-token [] (.getRequestToken twitter-service)) (defn get-twitter-url [token] (swap! tokens assoc (.getToken token) token) (.getAuthorizationUrl twitter-service token)) (defn get-access-token-twitter [oauth-token oauth-veri] (let [token (get @tokens oauth-token) verifier (Verifier. oauth-veri) access-token (.getAccessToken twitter-service token verifier)] (do (swap! tokens dissoc oauth-token)) access-token)) (defn get-info-twitter [access-token user-id] (let [request (doto (OAuthRequest. (Verb/GET) "https://api.twitter.com/1.1/users/show.json") (.addQuerystringParameter "user_id" user-id))] (do (.signRequest twitter-service access-token request)) (.getBody (.send request))))
and here the views:
(ns morgan.views.social (:use [noir.core :only [defpage]] [noir.response :only [redirect]] [morgan.models.social :only [get-access-token-twitter get-info-twitter get-twitter-url get-token]])) (defpage "/tw" {:keys [oauth_token oauth_verifier]} (let [access-token (get-access-token-twitter oauth_token oauth_verifier) user-id (:user_id ((fn [req] ;;homemade implementation of ;;URLDecode by amaloy ;;http://stackoverflow.com/a/6591708/869271 (into {} (for [[_ k v] (re-seq #"([^&=]+)=([^&]+)" req)] [(keyword k) v])) ) (.getRawResponse access-token)))] (get-info-twitter access-token user-id))) (defpage "/twitter" [] (redirect (get-twitter-url (get-token))))
The interface it’s the same of last time, a page “/twitter” (that tomorrow can be behind a button, a link, or whatever) that redirect to a page that twitter give to me, twitter then redirect the user back on my site “/tw” where I show what info I get when you give me the access.
Let’s analyze the model.
The first thing to notice would be on the line
(.provider (TwitterApi$Authenticate.))
TwitterApi$Authenticate is a inner class defined inside the TwitterApi class, you can use and import it like so.
In few line we can find the definition of a atom,
(def tokens (atom {}))
I hate use mutable in clojure but I didn’t find a better solution.
Twitter need the token to both generate the url where redirect the user so the user can authorize your app and to get the access-token (the token that give you the access to the user information). The token is defined by two string, a “public” one that you pass by GET more times and a secret one.
If a define a single immutable token then I can use it only with one user all the other request will fail, if I re-define the token every single time that I finish to use one the are some issue, take the example of a user who is already signed to my service and a very new user, the new user will take way more time of the old one to login, so while the new user is looking at the access I am getting the new user will generate a new token (destroying the old user one) and use it, as result the new user will not be able to sign in and he will need to try again. (I hope it is clear, if it is not just let me know)
Every time that I generate a new token I store it in the atom, I am using a map, so I use the string representation of the token as key and the object token like value.
(defn get-twitter-url [token] (swap! tokens assoc (.getToken token) token) ;;HERE (.getAuthorizationUrl twitter-service token))
And when I finish to work with the token I remove it from the map
(do
(swap! tokens dissoc oauth-token)) ;;HERE
access-token))
With the user authentication I can get some more info about the user itself with the function
get-info-twitter
That is pretty easy to understand.
To note is the page “/tw” in the view, it is where twitter redirect the user after the authentication, the page get two parameter, the verifier and the token, the same token that I am using like key to get the token object.
In the same response where I get the verifier and the token twitter send me also very little info about the user such user_id and screen_name, since I need at least one of this to get some info about the user I parse the response and the I ask twitter the basic info that I show you in the page.
Please note that the twitter app has permission “Read and Write”, asking also the direct message permission will make it a little more difficult.
You can try it out here:
https://morgan-siscia.rhcloud.com/twitter
Please note that if you visit the page twice after that you authenticate the first time it SHOULD NOT ask the permission again.
The code I am running behind the page is the same you are looking at, be aware that it might change in the future.
I hope that my english is fine, if you don’t get something just let me know in a comment.
Here the gits: https://gist.github.com/4218318