Progress is being made rapidly on StackMode, an Emacs client for StackExchange, and now we need to be able to make authenticated requests to the API for continued testing. (The 300-request limit is starting to limit how much testing I can do in a day.)
Disclaimer: I know very little about web development; it's one of the areas I'm working on professionally. Please excuse me if I misuse any terms and feel free to correct me in the comments. Thanks!
The StackExchange API uses OAuth 2.0 authentication. Since this is a local client application with client authorization. I have the following pieces of information provided to me by StackExchange:
with the following extra pieces of information:
In order to keep any answer both general and explicit, you can use my-client-id
(etc.) for values. Actual values—those I think I'm OK to share, are available on GitHub.
I've been researching this for half the day, but I'm not very much closer to a solution than when I started. The closest I've gotten is this little snippet of code:
(require 'oauth2) ; available via GNU ELPA
(defconst stack-auth-token
(make-oauth2-token
:client-id stack-auth--client-id
:client-secret stack-auth--key))
;; this doesn't use the above, but it does open an auth page on SE
(oauth2-auth-and-store
"https://stackexchange.com/oauth/dialog"
nil nil
stack-auth--client-id
stack-auth--key
"https://stackexchange.com/oauth/login_success")
The only things I have to offer an OAuth2 request (from above) are apparently
How can I implement this flow in Elisp?
oauth2-auth-and-store
with proper variables set.Opens
Opens
with this URL
The application is successfully added
But I have no code to provide oauth2
In addition to answers, PRs are also welcome, of course.
Here's a quick example. In short, this will open the auth url in the client browser, ask the user to allow the app, and then redirect to the /oauth/login_success
url as described in the docs (implicit auth).
This code prompts the user to paste the login_success
URL complete, then parses and saves the access_token
which can then be used for subsequent calls to the api. Two interactive function are defined: so-authenticate
which performs the auth steps described above, and so-read-inbox
which fetches the api data for the authenticated users inbox and dumps it to the messages buffer.
Warning, this example has no error handling!
At the very least you'll want to add checks for authentication failure, api request failures and token expiration. You can see an example api error by attempting to call so-read-inbox
before calling so-authenticate
.
To run, paste the following into a buffer, set the so--client-id
and so--client-key
variables then M-x eval-buffer
.
You can then use M-x so-authenticate
to authenticate and M-x so-read-inbox
to dump the inbox response.
(require 'json)
(defvar so--client-id "") ; SET THIS
(defvar so--client-key "") ; AND THIS
(defvar so--auth-url "https://stackexchange.com/oauth/dialog?")
(defvar so--redirect-url "https://stackexchange.com/oauth/login_success")
(defvar so--api-inbox-url "https://api.stackexchange.com/inbox?")
(defvar so--current-token nil) ; this will get set after authentication
(defun so-authenticate ()
(interactive)
(so--open-auth))
(defun so-read-inbox()
(interactive)
(so--retrieve-inbox))
;; Open auth url in browser and call so--get-save-token.
(defun so--open-auth ()
(let ((auth-url
(concat so--auth-url (url-build-query-string
`((client_id ,so--client-id)
(scope "read_inbox")
(redirect_uri ,so--redirect-url))))))
(browse-url auth-url))
(so--get-save-token))
;; Prompt user for callback URL, extract token and save in so--current-token
(defun so--get-save-token ()
(let* ((post-auth-url-string (read-string "Enter URL from your browser: "))
(token (nth 2 (split-string post-auth-url-string "[[#=&]"))))
(setq so--current-token token)
(message "Saved token: %S" token)))
;; Make a request for our inbox data
(defun so--retrieve-inbox()
(let ((inbox-url (concat so--api-inbox-url
(url-build-query-string
`((access_token ,so--current-token) ; the token from auth
(key ,so--client-key)))))) ; your client key
(url-retrieve inbox-url 'so--retrieve-inbox-cb)))
;; Parse json response for inbox request.
;; This simply dumps the parsed data to your messages buffer.
(defun so--retrieve-inbox-cb (status)
(goto-char (point-min))
(re-search-forward "^$")
(let ((inbox-data (json-read)))
(message "inbox data: %S" inbox-data)))
Now have fun parsing the response! :)
I'll try to answer as much of this as I can. I know absolutely nothing about Lisp, but I am very familiar with the Stack Exchange API and authorization flows.
"The 300-request limit is starting to limit how much testing I can do in a day."
You can upgrade this limit to 10,000 queries/day by appending your API key to the query string of method URLs (&key=...
).
"Actual values—those I think I'm OK to share, are available on GitHub."
Yup, you're safe to share those since any application shipping with those values can easily be reverse-engineered or decompiled to extract the values anyway.
"4. Opens [...] page [...] with this URL"
That is intended behavior. In your screenshot, authorization was successful and the URL's hash contains the access token. You will need this token to access certain methods, such as /inbox
.
What you probably want to do looks something like this:
access_token
and key
(the API key) parameters whenever invoking protected methods.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With