I am making a get and post requests like so:
(http-cljs.client/get "someurl.com/my")
and
(http-cljs.client/post "someurl.com/my")
On the server, I have the route:
{"/my" do-something}
While do-something runs with the get request, it doesn't with the post and in the client I get 403 forbidden. In the response I get "Invalid Anti-forgery token".
These are the middleware I'm using:
(defn config []
{:http-port (Integer. (or (env :port) 5000))
:middleware [[wrap-defaults site-defaults]
wrap-with-logger
wrap-gzip
[wrap-reload {:dir "../../src"}]
wrap-params
wrap-keyword-params
wrap-json-body
wrap-edn-params
]})
When I use api-defaults however, there's no 403 forbidden, and it only happens with the site-defaults. Why is this the case?
The configuration for wrap-defaults which is site-defaults will turn on the anti-forgery-middleware
If you look at the doc string of wrap-anti-forgery you will find:
"Middleware that prevents CSRF attacks. Any POST request to the handler
returned by this function must contain a valid anti-forgery token, or else an
access-denied response is returned.
The anti-forgery token can be placed into a HTML page via the
*anti-forgery-token* var, which is bound to a (possibly deferred) token.
The token is also available in the request under
`:anti-forgery-token`.
By default, the token is expected to be POSTed in a form field named
'__anti-forgery-token', or in the 'X-CSRF-Token' or 'X-XSRF-Token'
headers.
Accepts the following options:
:read-token - a function that takes a request and returns an anti-forgery
token, or nil if the token does not exist
:error-response - the response to return if the anti-forgery token is
incorrect or missing
:error-handler - a handler function to call if the anti-forgery token is
incorrect or missing
:strategy - a strategy for creating and validating anti-forgety tokens,
which must satisfy the
ring.middleware.anti-forgery.strategy/Strategy protocol
(defaults to the session strategy:
ring.middleware.anti-forgery.session/session-strategy)
Only one of :error-response, :error-handler may be specified.
Anti-forgery for forms is used to prevent replay attacks.
More info on CSRF attacks here: https://en.wikipedia.org/wiki/Cross-site_request_forgery
Related
I am trying to set up a Django API that receives POST requests with some JSON data and basically sends emails to a list of recipients. The logic is rather simple:
First I have the view for when I create a blog post. In the template, I include the csrf_token as specified on the Django Documentation. When I hit the submit button, behind the scene the create-post view, in addition to creating the post, makes a request (I am using the requests module) to the API which is charged with sending the emails. This is the piece of logic the sends the request to the API:
data = {
"title": new_post.title,
"summary": new_post.summary,
"link": var["BASE_URL"] + f"blog/post/{new_post.slug}"
}
csrf_token = get_token(request)
# print(csrf_token)
headers = {"X-CSRFToken": csrf_token}
requests.post(var["BASE_URL"] + "_api/send-notification/", json=data, headers=headers)
As you can see I am adding the X-CSRFToken to the headers which I generate through the get_token() method, as per the Django docs. However, the response in the API is a 403 Forbidden status CSRF Token not set.
I have checked the headers in the request and the token is indeed present. In addition, I have been providing other routes to the API and I have been using it for AJAX calls which again is very simple just follow the Django docs and they work perfectly well.
The problem seems to arise when I make the call from within the view, AJAX calls are handle by Javascript static files, and as I said they work fine.
I have thought that Django didn't allow the use of 2 CSRF tokens on the same page (one for the submit form and the other in the view by get_token()), but that's not the problem.
This is typically the error I get:
>>> Forbidden (CSRF cookie not set.): /_api/send-notification/
>>> "POST /_api/send-notification/ HTTP/1.1" 403 2864
I have read several similar questions on SO but they mostly involved using the csrf_exempt decorator, which in my opinion is not really a solution. It just gets rid of the CRSF token usefulness altogether.
Does anyone have any idea what might be causing this problem?
Thanks
Error tries to tell you that you need to add token into cookie storage like that:
cookies = {'csrftoken': csrf_token}
requests.post(var["BASE_URL"] + "_api/send-notification/", json=data, headers=headers, cookies=cookies)
In order to validate a captcha in Clojurescript I'm using cljs-http like this:
(def verify-url "https://www.google.com/recaptcha/api/siteverify")
;...
(http/post verify-url {:json-params {:secret key :response captcha-data}})
;...
...and the result is is:
{:status 0,
:success false,
:body "",
:headers {},
:trace-redirects ["https://www.google.com/recaptcha/api/siteverify"
"https://www.google.com/recaptcha/api/siteverify"],
:error-code :http-error,
:error-text " [0]"}
What does this mean?
Thanks!
Most probably your preflight (OPTIONS) requiest gets failed. Check your Developer Console for something like:
XMLHttpRequest cannot load
https://www.google.com/recaptcha/api/siteverify. Response to preflight
request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:3449' is therefore not allowed
access. The response had HTTP status code 405.
I used requests to send request.
import requests
def print(req):
print('{}\n{}\n{}\n\n{}'.format(
'-----------START-----------',
req.method + ' ' + req.url,
'\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()),
req.body,
))
print "----------END------------"
try:
req = requests.Request('GET',
'https://myip/myproject/upload/token',
headers={'Authorization': 'Token 401f7ac837a',
})
prepared = req.prepare()
print(prepared)
except Exception as e:
print "Exception:", e
Output:
-----------START-----------
GET https://myip/myproject/upload/token
Authorization: Token 401f7ac837a
None
----------END------------
But after I printed the request.META, there is
META:{u'CSRF_COOKIE': u'YGzoMaNEQJz1Kg8yXAwjJt6yNuT9L'
What set the CSRF_COOKIE?
Any comments welcomed. Thanks
UPDATE
(1)
From the doc, it said This cookie is set by CsrfViewMiddleware, which means the CSRF cookie was set in back-end and set to front-end in the response (CSRF cookie: server -> browser). Why it also said For all incoming requests that are not using HTTP GET, HEAD, OPTIONS or TRACE, a CSRF cookie must be present? And why it appears in my request.META? (CSRF cookie: browser -> server ???)
(2)
It said **A hidden form field with the name ‘csrfmiddlewaretoken’ present in all outgoing POST forms. The value of this field is the value of the CSRF cookie.
This part is done by the template tag.
**
When and How the template tag do it?
This is a standard cookie Django applications spin up for each new user to prevent Cross Site Forgery.
A CSRF cookie that is set to a random value (a session independent
nonce, as it is called), which other sites will not have access to.
This cookie is set by CsrfViewMiddleware. It is meant to be permanent,
but since there is no way to set a cookie that never expires, it is
sent with every response that has called
django.middleware.csrf.get_token() (the function used internally to
retrieve the CSRF token).
For security reasons, the value of the CSRF cookie is changed each
time a user logs in.
for more reading
https://docs.djangoproject.com/en/1.9/ref/csrf/#how-it-works
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)
})
Based on the documentation for Google's OAuth2 for web servers here, the flow consists of the following steps:
Your application redirects a browser to a Google URL; the URL includes query parameters that indicate the type of access being requested.
The result is an authorization code, which Google returns to your application in a query string.
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.
Using django.contrib.auth.views.login() to process user logins I'm seeing 403 responses in a production environment. A second attempt to login succeeds after an initial 403 (when that response occurs).
I've begun to log all 403 login failures, capturing the POST payload and cookie values which shows that csrfmiddlewaretoken (the hidden form field value) and csrftoken (cookie value) don't match. It's intermittent and happens to many users.
The following decorators are all applied to the login function being used to proxy the django.contrib.auth.views.login() function: #ensure_csrf_cookie, #sensitive_post_parameters, #csrf_protect, #never_cache
What might be the causes of this problem?
The CSRF token is rotated after login.
If you open the login page in one tab, login using a second tab, then you'll get a CSRF error if you submit the form on the original tab.