title: Flask WTF CSRF session token missing, secret_key not found
body: <p>I have researched every post I can find regarding "CSRF session token missing" in a Flask WTF app, but so far I cannot find the solution in any that have a solution <strong>or I am missing it and not seeing it</strong>.</p>
  
  <p>In this case I am creating a login page, and the error is generated on POST/submit of the login form.</p>
  
  <p>In Browser Dev tools I can see “csrf_token” in the Form Data but no token in the headers.</p>
  
  <p>The form data is coming from;</p>
  
  <pre><code> &lt;form method="POST" action=""&gt;
      {{ form.hidden_tag() }}
      {{ form.csrf_token() }}
  </code></pre>
  
  <p>In the login.html, but I don’t know if this is the expected result – it does not seem to be working.</p>
  
  <p>I was thinking I should see a X-CSRFToken In the request Headers ? But I do NOT.</p>
  
  <h2>Here is what I “think” I am doing correctly based on what I have researched and read on the topic for this error and configuration:</h2>
  
  <ol>
  <li>Am Using WTF FlaskForm</li>
  <li>Am using WTF CSRFProtect</li>
  <li>I DO have a SECRET_KEY set (I have tried the default and specifically for WTF)</li>
  <li>I am NOT excluding any views from CSRF</li>
  <li>I am using Flask-Login Login Manager</li>
  <li>Neither FireFox or Chome are blocking the “session” cookie and I can verify it is there in both browsers</li>
  <li>running on localhost:5000 and I also tried a specific domain like local.flask:5000</li>
  <li>I am only storing small strings (user_id) in the session</li>
  </ol>
  
  <p>Should it be a different cookie ? (e.g. named “csrf_token” not the “session” named cookie ?)</p>
  
  <h1>While debugging in the WTF csrf.py</h1>
  
  <p>in the validate_csrf() function, I find;</p>
  
  <pre><code>secret_key = _get_config(
      secret_key, 'WTF_CSRF_SECRET_KEY', current_app.secret_key,
      message='A secret key is required to use CSRF.'
  )
  </code></pre>
  
  <p>returns the expected secret value:</p>
  
  <pre><code>secret_key = {bytes} b'abc123ced456'
  field_name = _get_config(
      token_key, 'WTF_CSRF_FIELD_NAME', 'csrf_token',
      message='A field name is required to use CSRF.'
  )
  </code></pre>
  
  <p>returns  </p>
  
  <pre><code>field_name = {str} ‘csrf_token’
  </code></pre>
  
  <p><strong>and _data seems ok:</strong></p>
  
  <pre><code>data = {str} 'IjZiNWY5ZDdiNTZjMTVkM2U0Mzg3MjU1NGMxYzc3Yjg1MTMzYTlhYzEi.XC447w.cmc1INq6u8qVuq0EOL9ARcPwB6k'
  </code></pre>
  
  <p><strong>However it fails because “field_name” is not IN session</strong></p>
  
  <pre><code>if field_name not in session:
      raise ValidationError('The CSRF session token is missing.')
  </code></pre>
  
  <p>So the question is WHY ?</p>
  
  <p>I also get an error checking for the key/value from the login form method;</p>
  
  <pre><code>@app.route("/login", methods=['GET', 'POST'])
  def login():
      test = session['secret_key']
  </code></pre>
  
  <p>KeyError: 'secret_key'</p>
  
  <p>How does the app.secret_key get to the session ‘secret_key’ ?
  This appears to NOT be happening.</p>
  
  <h2>app.py</h2>
  
  <pre><code>from flask import Flask, render_template, url_for, flash, redirect,  Response, jsonify, abort, session
  from flask_session import Session
  from flask_wtf.csrf import CSRFProtect
  from flask_cors import CORS
  
  from flask_login import  LoginManager,UserMixin,current_user,login_required,login_user,logout_user
  
  from forms import RegistrationForm, LoginForm, TimecardForm
  from employees import employees
  
  csrf = CSRFProtect()
  
  app = Flask(__name__)
  csrf.init_app(app)
  
  app.config['SECRET_KEY'] = os.getenv('SECRET_KEY') or \
      'abc123ced456'
  
  app.config['SESSION_TYPE'] = 'memcached'
  app.config['WTF_CSRF_ENABLED'] = True
  app.config['WTF_CSRF_SECRET_KEY'] = os.getenv('SECRET_KEY') or \
      'abc123ced456'
  app.config['SESSION_COOKIE_SECURE'] =  True
  app.config['REMEMBER_COOKIE_SECURE'] =  True
  
  CORS(app)
  sess = Session()
  sess.init_app(app)
  
  
  login_manager = LoginManager()
  login_manager.init_app(app)
  login_manager.session_protection = "strong"
  login_manager.login_view = 'login'
  
  
  @login_manager.user_loader
  def load_user(userid):
      result = None
      emp_collection = employees.oEmployeeCollection()
      emp_collection.getAllEmployees(None, None)
      result = emp_collection.getEmployee(userid)
  
      return result
  
  @app.route("/login", methods=['GET', 'POST'])
  def login():
      form = LoginForm()
  
      if form.validate_on_submit():
          emp_collection = employees.oEmployeeCollection()
          emp_collection.getAllEmployees(None, None)
          current_user = emp_collection.getEmployee(form.user_init.data.upper())
  
          if current_user is not None:
              if current_user.password == form.password.data:
                  login_user(current_user, remember=True)
                  sess['current_user'] = current_user.toJSON()
  
                  flash('You have been logged in!', 'success')
  
                  #next = flask.request.args.get('next')
                  ## is_safe_url should check if the url is safe for redirects.
                  #if not is_safe_url(next):
                  #    return flask.abort(400)
                  #return flask.redirect(next or flask.url_for('index'))
  
                  return redirect(url_for('home'))
              else:
                  flash('Login Unsuccessful. Please check username and password', 'danger')
  
          else:
              flash('Login Unsuccessful. Please check username and password', 'danger')
  
      flash(form.errors)
      return render_template('login.html', title='Login', form=form)
  
  
  @app.before_first_request
  def execute_this():
      # emp_collection.getAllEmployees(None, None)
      test = None
  
  if __name__ == '__main__':
      app.run(host='flask.local', port=5000, debug=False)
  </code></pre>
  
  <h2>login.html</h2>
  
  <pre><code>{% extends "template.html" %}
  {% block content %}
      &lt;div class="content-section"&gt;
          &lt;form method="POST" action=""&gt;
              {{ form.hidden_tag() }}
              {{ form.csrf_token() }}
  
              &lt;fieldset class="form-group"&gt;
                  &lt;legend class="border-bottom mb-4"&gt;Log In&lt;/legend&gt;
  
                  &lt;div class="form-group"&gt;
                      {{ form.user_init.label(class="form-control-label")}}
                      {% if form.user_init.errors %}
                          {{ form.user_init(class="form-control form-control-lg is-invalid") }}
                          &lt;div class="invalid-feedback"&gt;
                              {% for error in form.user_init.errors %}
                                  &lt;span&gt;{{ error }}&lt;/span&gt;
                              {% endfor %}
                          &lt;/div&gt;
                      {% else %}
                          {{ form.user_init(class="form-control form-control-lg") }}
                      {% endif %}
                  &lt;/div&gt;
                  &lt;div class="form-group"&gt;
                      {{ form.password.label(class="form-control-label") }}
                      {% if form.password.errors %}
                          {{ form.password(class="form-control form-control-lg is-invalid") }}
                              &lt;div class="invalid-feedback"&gt;
                              {% for error in form.password.errors %}
                                  &lt;span&gt;{{ error }}&lt;/span&gt;
                              {% endfor %}
                          &lt;/div&gt;
                      {% else %}
                          {{ form.password(class="form-control form-control-lg") }}
                      {% endif %}
                  &lt;/div&gt;
                  &lt;div class="form-check"&gt;
                      {{ form.remember(class="form-check-input") }}
                      {{ form.remember.label(class="form-check-label") }}
                  &lt;/div&gt;
              &lt;/fieldset&gt;
              &lt;div class="form-group"&gt;
                  {{ form.submit(class="btn btn-outline-info") }}
              &lt;/div&gt;
              &lt;small class="text-muted ml-2"&gt;
                  &lt;a href="#"&gt;Forgot Password?&lt;/a&gt;
              &lt;/small&gt;
          &lt;/form&gt;
      &lt;/div&gt;
      &lt;div class="border-top pt-3"&gt;
          &lt;small class="text-muted"&gt;
              Need An Account? &lt;a class="ml-2" href="{{ url_for('register') }}"&gt;Sign Up Now&lt;/a&gt;
          &lt;/small&gt;
      &lt;/div&gt;
  {% endblock content %}
  </code></pre>
  
  <h2>Forms.py</h2>
  
  <pre><code>from flask_wtf import FlaskForm
  from wtforms import StringField, PasswordField, SubmitField,      BooleanField, DateField, DecimalField
  from wtforms.validators import DataRequired, Length, Email, EqualTo
  
  class LoginForm(FlaskForm):
      user_init = StringField('User',  validators=[DataRequired()])
      password = PasswordField('Password', validators=[DataRequired()])
      remember = BooleanField('Remember Me')
      submit = SubmitField('Login')
  </code></pre>
  
  <h1>Request results</h1>
  
  <h2>Response</h2>
  
  <pre><code>&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"&gt;
  &lt;title&gt;400 Bad Request&lt;/title&gt;
  &lt;h1&gt;Bad Request&lt;/h1&gt;
  &lt;p&gt;The CSRF session token is missing.&lt;/p&gt;
  </code></pre>
  
  <h2>Session cookie</h2>
  
  <pre><code>Content-Type →text/html
  Content-Length →142
  Access-Control-Allow-Origin →*
  Set-Cookie →session=ad0a88f2-4048-4a3b-9934-c2cd5957e9ff; Expires=Sun, 03-Feb-2019 14:55:27 GMT; HttpOnly; Path=/
  Server →Werkzeug/0.14.1 Python/3.7.1
  Date →Thu, 03 Jan 2019 14:55:27 GMT
  </code></pre>
  
  <h2>Request General</h2>
  
  <pre><code>Request URL: http://localhost:5000/login
  Request Method: POST
  Status Code: 400 BAD REQUEST
  Remote Address: 127.0.0.1:5000
  Referrer Policy: no-referrer-when-downgrade
  </code></pre>
  
  <h2>Response Headers</h2>
  
  <pre><code>Access-Control-Allow-Origin: http://localhost:5000
  Content-Length: 150
  Content-Type: text/html
  Date: Thu, 03 Jan 2019 14:47:18 GMT
  Server: Werkzeug/0.14.1 Python/3.7.1
  Set-Cookie: session=62e6139c-332b-4811-ad3a-de5c29c878aa; Expires=Sun, 03-Feb-2019 14:47:18 GMT; HttpOnly; Path=/
  Vary: Origin
  </code></pre>
  
  <h2>Request Headers</h2>
  
  <pre><code>POST /login HTTP/1.1
  Host: localhost:5000
  Connection: keep-alive
  Content-Length: 258
  Cache-Control: max-age=0
  Upgrade-Insecure-Requests: 1
  User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
  Origin: http://localhost:5000
  Content-Type: application/x-www-form-urlencoded
  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
  Referer: http://localhost:5000/login
  Accept-Encoding: gzip, deflate, br
  Accept-Language: en-US,en;q=0.9
  
  Cookie: Webstorm-655f3561=d5da8892-b9fc-4680-8fe8-17baf5fd6f8d;session=62e6139c-332b-4811-ad3a-de5c29c878aa
  </code></pre>
  
  <h2>Form Data</h2>
  
  <pre><code>csrf_token=ImI5ZDlkYjZmNjkxMDZlZDczZjdlY2VjMTM2NTQzOWZlMDBkYTY1ZWMi.XC4gZQ.DVyKZ07nrQN6WZn0jmoHyKrf_YI&amp;
          csrf_token=ImI5ZDlkYjZmNjkxMDZlZDczZjdlY2VjMTM2NTQzOWZlMDBkYTY1ZWMi.XC4gZQ.DVyKZ07nrQN6WZn0jmoHyKrf_YI&amp;user_init=ABC&amp;password=changeme&amp;remember=y&amp;submit=Login
  </code></pre>