title: "Missing CSRF token in headers" with JWT authentication in Flask-Admin
body: <p>I am writing a Flask backend to serve a RESTful API with authentication through JWT.</p>
  
  <p>I am using Flask-Admin for the admin and, in order to use a single authentication method, I would like to use JWT authentication with it too. </p>
  
  <p>I added a simple login form to Flask-Admin that set the JWT tokens in the cookies.</p>
  
  <p>The JWT tokens are found and properly decoded in the request in the <code>create_form</code> method of my <code>sqla.ModelView</code>, but when posting the form (by clicking on the <code>save</code> button of my create form, I get a 401 error: <b>"Missing CSRF token in headers</b>.</p>
  
  <p>Anybody can help ?</p>
  
  <p><b>Login route</b></p>
  
  <pre><code>@bp_api.route('/api/user/login', methods=('POST',))
  def login():
  
      data = request.json
  
      web = request.args.get('web', 0, type=int)
  
  
      user = User.query.filter(User.username==data['username']).first()
  
      if not user:
          return jsonify({'msg': 'User {} doesn\'t exist'.format(data['username'])}), 400
  
      if not user.confirmed_on:
          return jsonify({'msg': f'{user.email} not confirmed'}), 400
  
      if check_password_hash(user.password, data['password']):
          access_token = create_access_token(identity = data['username'], fresh=True)
          refresh_token = create_refresh_token(identity = data['username'])
          if web:
              resp = jsonify({'username': f'{user.username}'})
              set_access_cookies(resp, access_token)
              set_refresh_cookies(resp, refresh_token)
          else:
              resp = jsonify({'msg':f'Logged as {user.username}',
                  'access_token': access_token, 'refresh_token': refresh_token}
              )
          return resp, 200
  
      else:
          return jsonify({'msg': 'Invalid password.'}), 401
  </code></pre>
  
  <p><b>Flask-Admin view</b></p>
  
  <pre><code>from flask_admin.base import BaseView
  from flask_admin.contrib import sqla
  
  from flask_jwt_extended import (
      get_current_user,
      get_jwt_identity,
      jwt_required,
      verify_jwt_in_request,
      verify_jwt_refresh_token_in_request,
      create_access_token,
      set_access_cookies,
      unset_jwt_cookies,
  )
  from flask_jwt_extended.exceptions import NoAuthorizationError
  from jwt import ExpiredSignatureError
  
  
  
  class PhaunosBaseView(BaseView):
  
      def render(self, template, **kwargs):
      try:
          verify_jwt_in_request()
          self._template_args['current_user'] = get_current_user()
          current_app.logger.info("Access token ok for user {}".format(get_current_user()))
          resp = make_response(super(PhaunosBaseView, self).render(template, **kwargs))
  
      except ExpiredSignatureError:
          # if the access token has expired, create new non-fresh token
          current_app.logger.info("Access token has expired.")
          try:
              verify_jwt_refresh_token_in_request()
              self._template_args['current_user'] = get_current_user()
              access_token = create_access_token(identity=get_jwt_identity(), fresh=False)
              resp = make_response(super(PhaunosBaseView, self).render(template, **kwargs))
              set_access_cookies(resp, access_token)
          except ExpiredSignatureError:
              # if the refresh token has expired, user must login again
              current_app.logger.info("Refresh token has expired")
              resp = make_response(super(PhaunosBaseView, self).render(template, **kwargs))
              unset_jwt_cookies(resp)
      except NoAuthorizationError:
          current_app.logger.info("No authorization token.")
          resp = make_response(super(PhaunosBaseView, self).render(template, **kwargs))
      return resp
  
  
  class PhaunosModelView(PhaunosBaseView, sqla.ModelView):
      pass    
  
  
  class ProjectAdminView(PhaunosModelView):   
  
      def create_form(self, obj=None):
          current_app.logger.info("In create_form")
          form = super(ProjectAdminView, self).create_form(obj)
          current_app.logger.info(request.headers)
          verify_jwt_in_request()
          return form
  
      def on_model_change(self, form, model, is_created):
          current_app.logger.info("In on_model_change")
          current_app.logger.info(id(request))
          current_app.logger.info(request.headers)
          verify_jwt_in_request()
          # then set get_current_user() to some model attribute
  
  </code></pre>
  
  <p><b>log</b></p>
  
  <pre><code>[2019-03-28 18:20:27,311] INFO in views: In create_form
  [2019-03-28 18:20:27,312] INFO in views: 140045316455232
  [2019-03-28 18:20:27,312] INFO in views: Host: 127.0.0.1:5000
  User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0
  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
  Accept-Language: en-US,en;q=0.5
  Accept-Encoding: gzip, deflate
  Referer: http://127.0.0.1:5000/admin/admin_project/
  Connection: keep-alive
  Cookie: session_id=0c89eb809ae01aa65aa58bbc45faf79b31af32db; access_token_cookie=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NTM3OTcxOTYsIm5iZiI6MTU1Mzc5NzE5NiwianRpIjoiMWU0ZTA3YzItMjJjOC00NTFhLWJkNjAtYjllNzZmNWYzMTYyIiwiZXhwIjoxNTUzNzk3Mzc2LCJpZGVudGl0eSI6ImR1bW15X3VzZXI0MiIsImZyZXNoIjp0cnVlLCJ0eXBlIjoiYWNjZXNzIiwiY3NyZiI6IjkwZTY5ZjM1LWQwNDctNDM0NC1hOWVlLTg2NDM4OWFiNzM4OCJ9.0p1CBGxpUzLgIR369I1yP-FTRel4-fhAIeL084YV_O0; csrf_access_token=90e69f35-d047-4344-a9ee-864389ab7388; refresh_token_cookie=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NTM3OTcxOTYsIm5iZiI6MTU1Mzc5NzE5NiwianRpIjoiODI0MGJkODItYjg0NC00MjI5LWFiN2MtZTRhN2IwYzgxYzYzIiwiZXhwIjoxNTUzNzk3NTU2LCJpZGVudGl0eSI6ImR1bW15X3VzZXI0MiIsInR5cGUiOiJyZWZyZXNoIiwiY3NyZiI6Ijg1M2FiYzk0LWU2YjctNGZkZi05YzRhLTczNDMxZWE4NGRlOCJ9.CcHzlWsT9kBURZ1FaeCjG8du7FGXLD-fm5-HF0sBdJ8; csrf_refresh_token=853abc94-e6b7-4fdf-9c4a-73431ea84de8
  Upgrade-Insecure-Requests: 1
  
  
  [2019-03-28 18:20:27,316] INFO in views: Access token ok for user dummy_user42
  172.23.0.1 - - [28/Mar/2019 18:20:27] "GET /admin/admin_project/new/?url=%2Fadmin%2Fadmin_project%2F HTTP/1.1" 200 -
  [2019-03-28 18:21:11,496] INFO in views: In create_form
  [2019-03-28 18:21:11,496] INFO in views: 140045316455232
  [2019-03-28 18:21:11,496] INFO in views: Host: 127.0.0.1:5000
  User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0
  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
  Accept-Language: en-US,en;q=0.5
  Accept-Encoding: gzip, deflate
  Referer: http://127.0.0.1:5000/admin/admin_project/new/?url=%2Fadmin%2Fadmin_project%2F
  Content-Type: multipart/form-data; boundary=---------------------------209115246111667105161673380
  Content-Length: 857
  Connection: keep-alive
  Cookie: session_id=0c89eb809ae01aa65aa58bbc45faf79b31af32db; access_token_cookie=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NTM3OTcxOTYsIm5iZiI6MTU1Mzc5NzE5NiwianRpIjoiMWU0ZTA3YzItMjJjOC00NTFhLWJkNjAtYjllNzZmNWYzMTYyIiwiZXhwIjoxNTUzNzk3Mzc2LCJpZGVudGl0eSI6ImR1bW15X3VzZXI0MiIsImZyZXNoIjp0cnVlLCJ0eXBlIjoiYWNjZXNzIiwiY3NyZiI6IjkwZTY5ZjM1LWQwNDctNDM0NC1hOWVlLTg2NDM4OWFiNzM4OCJ9.0p1CBGxpUzLgIR369I1yP-FTRel4-fhAIeL084YV_O0; csrf_access_token=90e69f35-d047-4344-a9ee-864389ab7388; refresh_token_cookie=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NTM3OTcxOTYsIm5iZiI6MTU1Mzc5NzE5NiwianRpIjoiODI0MGJkODItYjg0NC00MjI5LWFiN2MtZTRhN2IwYzgxYzYzIiwiZXhwIjoxNTUzNzk3NTU2LCJpZGVudGl0eSI6ImR1bW15X3VzZXI0MiIsInR5cGUiOiJyZWZyZXNoIiwiY3NyZiI6Ijg1M2FiYzk0LWU2YjctNGZkZi05YzRhLTczNDMxZWE4NGRlOCJ9.CcHzlWsT9kBURZ1FaeCjG8du7FGXLD-fm5-HF0sBdJ8; csrf_refresh_token=853abc94-e6b7-4fdf-9c4a-73431ea84de8
  Upgrade-Insecure-Requests: 1
  
  
  172.23.0.1 - - [28/Mar/2019 18:21:11] "POST /admin/admin_project/new/?url=%2Fadmin%2Fadmin_project%2F HTTP/1.1" 401 -
  </code></pre>