federicotdn / verb

Organize and send HTTP requests from Emacs
https://melpa.org/#/verb
GNU General Public License v3.0
540 stars 20 forks source link

How auth can be added to the request, and reuse the token? #73

Open DevGiuDev opened 3 months ago

DevGiuDev commented 3 months ago

Hi,

I'm struggling with this without success. Any sample I see related to verb or restclient use auth. I'm trying to migrate from Insomnia to eMacs with ORG+verb

I'm starting to the login API request. I have to do a POST request to http://localhost:8081/backend/rest/v2/oauth/token

The cUrl command is as follows:

curl --request POST \
  --url http://localhost:8081/backend/rest/v2/oauth/token \
  --header 'Authorization: Basic YXBwYnV5YmFja2VuZC1lcnZEVUdmUjo5YTZhMzUzMjMzZTA4MDgxZDcyMWJjZWM3NDkwOTNhZGU5MTcyZGZkNWM3ZmViYjgxMjc4OWM2ZGRkZmVhMzYy' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data username=admin \
  --data password=admin \
  --data grant_type=password

And this is what I have

* GetToken                                                             :verb:
#+begin_src verb
POST http://localhost:8081/backend/rest/v2/oauth/token
Authorization: Basic YXBwYnV5YmFja2VuZC1lcnZEVUdmUjo5YTZhMzUzMjMzZTA4MDgxZDcyMWJjZWM3NDkwOTNhZGU5MTcyZGZkNWM3ZmViYjgxMjc4OWM2ZGRkZmVhMzYy
Content-Type: application/x-www-form-urlencoded

username=admin
password=admin
grant_type=password    
#+end_src

But the API always tells me:

{
  "error": "invalid_request",
  "error_description": "Missing grant type"
}

and I expect to get something like:

{
    "access_token": "aYm2RV_bOK5LKEQRcXLqS-_a3_w",
    "token_type": "bearer",
    "refresh_token": "RoEHzx2MkjKUP91Ew5WJhtFHQZs",
    "expires_in": 43199,
    "scope": "rest-api"
}

Where I would like to get the access_token to use it in other requests as Bearer.

One thing I do on Insomnia, and I don't know if could be possible here, is that if I call another request, getToken will be called to request a token if necessary.

Thanks.

armkeh commented 3 months ago

I don't think it is possible to split the raw data into multiple lines like you have. When I copy that Verb block as curl (C-c C-e C-r c), it gives

 curl 'http://localhost:8081/backend/rest/v2/oauth/token' \
 -H 'Authorization: Basic YXBwYnV5YmFja2VuZC1lcnZEVUdmUjo5YTZhMzUzMjMzZTA4MDgxZDcyMWJjZWM3NDkwOTNhZGU5MTcyZGZkNWM3ZmViYjgxMjc4OWM2ZGRkZmVhMzYy' \
 -H 'Content-Type: application/x-www-form-urlencoded' \
 -X POST \
 --data-raw 'username=admin
 password=admin
 grant_type=password    '

You could join it all on one line using & as delimiters:

* GetToken                                                             :verb:
#+begin_src verb
POST http://localhost:8081/backend/rest/v2/oauth/token
Authorization: Basic YXBwYnV5YmFja2VuZC1lcnZEVUdmUjo5YTZhMzUzMjMzZTA4MDgxZDcyMWJjZWM3NDkwOTNhZGU5MTcyZGZkNWM3ZmViYjgxMjc4OWM2ZGRkZmVhMzYy
Content-Type: application/x-www-form-urlencoded

username=admin&password=admin&grant_type=password    
#+end_src

Or, more readably in my opinion, use an Elisp snippet:

* GetToken                                                             :verb:
 #+begin_src verb
 POST http://localhost:8081/backend/rest/v2/oauth/token
 Authorization: Basic YXBwYnV5YmFja2VuZC1lcnZEVUdmUjo5YTZhMzUzMjMzZTA4MDgxZDcyMWJjZWM3NDkwOTNhZGU5MTcyZGZkNWM3ZmViYjgxMjc4OWM2ZGRkZmVhMzYy
 Content-Type: application/x-www-form-urlencoded

 {{(mapconcat 'identity '("username=admin" "password=admin" "grant_type=password") "&")}}
 #+end_src

(But the snippet needs to be all on one line; you could probably break it up using a noweb reference if you have a lot more data to include.)

Either of these give a correct looking command when copied to curl:

curl 'http://localhost:8081/backend/rest/v2/oauth/token' \
 -H 'Authorization: Basic YXBwYnV5YmFja2VuZC1lcnZEVUdmUjo5YTZhMzUzMjMzZTA4MDgxZDcyMWJjZWM3NDkwOTNhZGU5MTcyZGZkNWM3ZmViYjgxMjc4OWM2ZGRkZmVhMzYy' \
 -H 'Content-Type: application/x-www-form-urlencoded' \
 -X POST \
 --data-raw 'username=admin&password=admin&grant_type=password'
armkeh commented 3 months ago

Regarding re-using the token, here is how I've been doing that mostly automatically:

This code block will let us extract elements from the JSON of a response:

#+name: extract-http-response-json
#+begin_src emacs-lisp
;; Search for the JSON contents by looking for a pair of curly braces;
;; if unexpected behaviour occurs, check if the response has extra braces, and if so, this might need to be made smarter
(let ((resp-json (if (string-match "{[^b-a]*}" response) ;; Silly regexp to match all characters including newline (`b-a` is an empty range, so ^-ing it matches all characters)
                     (match-string 0 response))))
  (apply 'verb-json-get resp-json token-path))
#+end_src

Then this code block uses the above to extract the access and refresh tokens specifically:

#+name: extract-auth-tokens
#+begin_src emacs-lisp :var response="" :noweb yes :noweb-prefix no
(verb-var auth-token "") ;; Verb variable must be set before using verb-set-var, so set it to a default "" if not set yet
(let ((token-path '("access_token")))
  (verb-set-var "auth-token" <<extract-http-response-json>>))

(verb-var refresh-token "")
(let ((token-path '("refresh_token")))
  (verb-set-var "refresh-token" <<extract-http-response-json>>))

;; Echo the raw response for the results block, followed by info on the extracted values
(concat response "\n\n" (format "extracted keycloak-auth-token: %s\nextracted keycloak-refresh-token: %s"
  (verb-var auth-token) (verb-var refresh-token)))
#+end_src

Add the below :post header argument to your token request to use the above to extract the tokens:

#+begin_src verb :wrap src ob-verb-response :post extract-auth-tokens(*this*)
POST http://localhost:8081/backend/rest/v2/oauth/token
Content-Type: application/x-www-form-urlencoded

{{(mapconcat 'identity '("username=admin" "password=admin" "grant_type=password") "&")}}
#+end_src

And then you can use the auth token in all your requests by setting it in a top-level heading, and putting all requests that require authentication under that top-level heading:

* My API                                      :verb:
template http://localhost:8081/backend/rest/v2
Authorization: Bearer {{(verb-var auth-token)}}

** Request 1

...

** Request 2

...