You can override the login form when you are setting up Flask-Security:

user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore, login_form=CustomLoginForm)

Then create your custom login form class that extends the default LoginForm. And override the validate function to do stuff before or after the login attempt.

from flask_security.forms import LoginForm

class CustomLoginForm(LoginForm):
    def validate(self):
        # Put code here if you want to do stuff before login attempt

        response = super(CustomLoginForm, self).validate()

        # Put code here if you want to do stuff after login attempt

        return response

"reponse" will be True or False based on if the login was successful or not. If you want to check possible errors that occurred during the login attempt see "self.errors"

Greetings, Kevin ;)

Answer from Kevin on Stack Overflow
🌐
GitHub
github.com › mattupstate › flask-security-example › blob › master › templates › security › login.html
flask-security-example/templates/security/login.html at master · mattupstate/flask-security-example
February 22, 2024 - {% include "security/_messages.html" %} <h1>Custom Login Form</h1> <form action="{{ url_for_security('login') }}" method="POST" name="login_form"> {{ login_form.hidden_tag() }} {{ render_field_with_errors(login_form.email) }} {{ render_field_with_errors(login_form.password) }} {{ render_field_with_errors(login_form.remember) }} {{ render_field(login_form.next) }} {{ render_field(login_form.submit) }} </form> {% include "security/_menu.html" %} {% endblock %}
Author   mattupstate
Discussions

python - How do I embed a Flask-Security login form on my page? - Stack Overflow
Using the example code provided by Flask-Security, I can access the login_user.html form from the /login route normally and that works just fine. However, I would like to embed the login form on al... More on stackoverflow.com
🌐 stackoverflow.com
How to customize flask.ext.security.forms.LoginForm to prompt and use username instead of email? - Stack Overflow
Finally, create the custom login form. from flask_security.forms import LoginForm from wtforms import StringField from wtforms.validators import InputRequired class ExtendedLoginForm(LoginForm): email = StringField('Username or Email Address', [InputRequired()]) # Setup Flask-Security ... More on stackoverflow.com
🌐 stackoverflow.com
June 7, 2016
Flask-Security with Flask-Admin, how to use two different login forms?

I think your best bet is just to copy flask_security's login form and view into your project, modify as necessary so you get the behaviour you want specific to the admin, and then register it as a separate endpoint/view. It can share the same blueprint you're using for your other admin views, and you can register your admin-specific context processor with that blueprint, so it only gets included where its needed. So all of flask_security continues to use the security.login endpoint, as well as the "public facing" login. Your admins, however, use your custom endpoint.

Example code (untested, there might be bugs, but hopefully it should get you close):

make a custom login form for the admin:

from flask_security.forms import LoginForm


class AdminLoginForm(LoginForm):
    """
    extend/override stuffs as necessary. this is a stock Flask-WTF form
    """

use it in your custom admin login view:

from flask import redirect, render_template, request
from flask_security.utils import login_user
from flask_security.views import after_this_request, _commit


@bp.route('/admin/login', endpoint='admin.login', methods=['GET', 'POST'])
def admin_login():
    form = AdminLoginForm(request.form)
    if form.validate_on_submit():
        login_user(form.user, remember=form.remember.data)
        
        # only necessay if you have `SECURITY_TRACKABLE = True` in your config
        after_this_request(_commit)
        
        return redirect(form.next.data or '/admin')

    return render_template('admin/login.html', form=form)

configure your admins to use it:

from flask import abort, redirect, request, url_for
from flask_admin import AdminIndexView, expose
from flask_admin.contrib.sqla import ModelView as BaseModelView
from flask_security import current_user
from http import HTTPStatus


class AdminSecurityMixin:
    def is_accessible(self):
        if (current_user.is_active  # is current_user account enabled?
                # are they logged in?
                and current_user.is_authenticated
                # do they have the correct roles to login to the admin?
                and current_user.has_role('ROLE_ADMIN')):
            return True
        return False

    def _handle_view(self, name, **kwargs):
        if not self.is_accessible():
            # if user is logged in, but can't view the admin, reject access
            if current_user.is_authenticated:
                abort(HTTPStatus.FORBIDDEN)
            # otherwise redirect to the admin login view
            # the next parameter is so we can redirect them after they'ved
            # logged in to where they wanted to go originally
            return redirect(url_for('admin.login', next=request.url))


class ModelView(AdminSecurityMixin, BaseModelView):
    """ 
    all your model admins should extend this base class, so that they are secured
    
    this is a great place to put other stuffs common to all your admins too
    """


class AdminDashboard(AdminSecurityMixin, AdminIndexView):
    @expose('/')
    def index(self):
        return self.render('admin/dashboard.html')

(probably those three admin classes shouldn't be in the same file, but, that's up to you. also, obviously I've left out the templates, but you'll need those too)

More on reddit.com
🌐 r/flask
1
7
March 29, 2018
python - Flask-Security: Customizing Registration - Stack Overflow
I'm trying to use Flask-Security to login and register users. However, I'm trying to do two things. The first being change the default /registration to /signup which I believe I've done correctly. The second is I want to customize the registration form. I can get the page loaded but whenever ... More on stackoverflow.com
🌐 stackoverflow.com
March 8, 2017
🌐
Readthedocs
flask-security-too.readthedocs.io › en › stable › customizing.html
Customizing — Flask-Security 5.8.1 documentation
This is an example of how to modify the registration and login form to add support for a single input field to accept both email and username (mimicking legacy Flask-Security behavior).
Top answer
1 of 2
15

security/login_user.html is the full page template for the login form. It's not what you would want to embed on each page because it deals with other things like flashed messages, errors, and layout.

Write your own template to render just the form and include that, or add it to your base template.

<form action="{{ url_for_security('login') }}" method="POST">
  {{ login_user_form.hidden_tag() }}
  {{ login_user_form.email(placeholder='Email') }}
  {{ login_user_form.password(placeholder='Password') }}
  {{ login_user_form.remember.label }} {{ login_user_form.remember }}
  {{ login_user_form.submit }}
</form>

(This is just an example, you'll want to style it to fit your page.)

None of your views will deal with the login form directly, so login_form and url_for_security aren't available while rendering most templates (this was causing the original issue you observed). Inject them for each request using app.context_processor.

from flask_security import LoginForm, url_for_security

@app.context_processor
def login_context():
    return {
        'url_for_security': url_for_security,
        'login_user_form': LoginForm(),
    }
2 of 2
1

Your stack trace reads: jinja2.exceptions.UndefinedError: 'login_user_form' is undefined

This error message is coming from Jinja, which is saying that login_user_form is undefined.

To solve this, if this was your base.html,

...
{% block content %}
<form action="" method="post" name="post">
    {{form.hidden_tag()}}
...

you've to pass login_user_form as a context variable to your render template. So, in your /login route, you should return something like this:

return render_template("base.html",
        title = 'Home',
        form = login_user_form)
Top answer
1 of 2
2

To login with a name instead of an email address (using Flask-Security 1.7.0 or higher), you can replace the email field with a name field in the User model

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), unique=True, index=True)
    password = db.Column(db.String(255))
    active = db.Column(db.Boolean())
    confirmed_at = db.Column(db.DateTime())
    roles = db.relationship('Role', secondary=roles_users,
                            backref=db.backref('users', lazy='dynamic'))

and update the app configuration.

app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = 'name'

Next, to allow users to login using a name instead of an email, we will use the fact that the LoginForm validation method assumes the user identity attribute is in the email form field.

from flask_security.forms import LoginForm
from wtforms import StringField
from wtforms.validators import InputRequired

class ExtendedLoginForm(LoginForm):
    email = StringField('Username', [InputRequired()])

# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore,
                    login_form=ExtendedLoginForm)

This way, we can login using a name without rewriting the validation method or the login template. Of course, this is a hack and the more correct approach would be to add a custom validate method (which checks a name form field) to the ExtendedLoginForm class and to update the login template accordingly (as done by Raja R above). Or even better, to replace the 'email' form field in 'LoginForm' with a 'user_identity' field and rewrite the validate method to accommodate any user identity field.

However, the approach above makes it easy to login with a name or an email address. To do this, define a user model with both a name and email field.

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True)
    name = db.Column(db.String(255), unique=True, index=True)
    password = db.Column(db.String(255))
    active = db.Column(db.Boolean())
    confirmed_at = db.Column(db.DateTime())
    roles = db.relationship('Role', secondary=roles_users,
                            backref=db.backref('users', lazy='dynamic'))

and update the app configuration.

app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ('name','email')

Finally, create the custom login form.

from flask_security.forms import LoginForm
from wtforms import StringField
from wtforms.validators import InputRequired

class ExtendedLoginForm(LoginForm):
    email = StringField('Username or Email Address', [InputRequired()])

# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore,
                    login_form=ExtendedLoginForm)

Now, when logging in, Flask-Security will accept an email or name in the email form field.

2 of 2
1

In earlier versions of flask-security, the unique identifier of the user was the email property. You could extend models by adding your own properties, but the email field still served to uniquely identify the user (so the username had to be the email field). From version 1.7.0 it is possible to identify user with a different property. In the config file you should add the

app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = 'name'

But you still have to have the email property in your model. There was a issue reported 3 days on git, for exactly what you are looking for https://github.com/mattupstate/flask-security/issues/396

🌐
Reddit
reddit.com › r/flask › flask-security with flask-admin, how to use two different login forms?
r/flask on Reddit: Flask-Security with Flask-Admin, how to use two different login forms?
March 29, 2018 -

I am building a flask boilerplate site and I am using flask admin so I can create users (its an "internal" site with no registrations).

So I have a login form where the admin can login and create user, but I also want to have a regular login where users can log into the site.

They're pretty much the same login just a different form and look? Is that possible or should I have them route to the same form and then have non admin redirect to the home page and the admin can redirect to the admin backend?

Right now I am using a context processor to hook into flask-admin:

# define a context processor for merging flask-admin's template context into the
# flask-security views.
@security.context_processor
def security_context_processor():
    return dict(
        admin_base_template=admin.base_template,
        admin_view=admin.index_view,
        h=admin_helpers,
        get_url=url_for
    )

So that the url_for('security.login') will direct you to /mysite/admin/login? Is there a way to break it so that, url_for('security.admin.login') directs to /mysite/admin/login and url_for('security.login') directs to /mysite/login?

Something along those lines.

Top answer
1 of 1
2

I think your best bet is just to copy flask_security's login form and view into your project, modify as necessary so you get the behaviour you want specific to the admin, and then register it as a separate endpoint/view. It can share the same blueprint you're using for your other admin views, and you can register your admin-specific context processor with that blueprint, so it only gets included where its needed. So all of flask_security continues to use the security.login endpoint, as well as the "public facing" login. Your admins, however, use your custom endpoint.

Example code (untested, there might be bugs, but hopefully it should get you close):

make a custom login form for the admin:

from flask_security.forms import LoginForm


class AdminLoginForm(LoginForm):
    """
    extend/override stuffs as necessary. this is a stock Flask-WTF form
    """

use it in your custom admin login view:

from flask import redirect, render_template, request
from flask_security.utils import login_user
from flask_security.views import after_this_request, _commit


@bp.route('/admin/login', endpoint='admin.login', methods=['GET', 'POST'])
def admin_login():
    form = AdminLoginForm(request.form)
    if form.validate_on_submit():
        login_user(form.user, remember=form.remember.data)
        
        # only necessay if you have `SECURITY_TRACKABLE = True` in your config
        after_this_request(_commit)
        
        return redirect(form.next.data or '/admin')

    return render_template('admin/login.html', form=form)

configure your admins to use it:

from flask import abort, redirect, request, url_for
from flask_admin import AdminIndexView, expose
from flask_admin.contrib.sqla import ModelView as BaseModelView
from flask_security import current_user
from http import HTTPStatus


class AdminSecurityMixin:
    def is_accessible(self):
        if (current_user.is_active  # is current_user account enabled?
                # are they logged in?
                and current_user.is_authenticated
                # do they have the correct roles to login to the admin?
                and current_user.has_role('ROLE_ADMIN')):
            return True
        return False

    def _handle_view(self, name, **kwargs):
        if not self.is_accessible():
            # if user is logged in, but can't view the admin, reject access
            if current_user.is_authenticated:
                abort(HTTPStatus.FORBIDDEN)
            # otherwise redirect to the admin login view
            # the next parameter is so we can redirect them after they'ved
            # logged in to where they wanted to go originally
            return redirect(url_for('admin.login', next=request.url))


class ModelView(AdminSecurityMixin, BaseModelView):
    """ 
    all your model admins should extend this base class, so that they are secured
    
    this is a great place to put other stuffs common to all your admins too
    """


class AdminDashboard(AdminSecurityMixin, AdminIndexView):
    @expose('/')
    def index(self):
        return self.render('admin/dashboard.html')

(probably those three admin classes shouldn't be in the same file, but, that's up to you. also, obviously I've left out the templates, but you'll need those too)

🌐
GitHub
github.com › mattupstate › flask-security › issues › 474
security templates with bootstrap · Issue #474 · pallets-eco/flask-security-3.0
January 29, 2016 - is there a way to avoid copying the security templates to the local app? how can I just include the site-package/flask_security/templates into a generic page template? maybe interrupt the render_template from that view.login from the main app via some kind of pre-process? 85 return _security.render_template(config_value('LOGIN_USER_TEMPLATE'), 86 login_user_form=form, 87 **_ctx('login')) was able to customize the security.login page by creating the local application /templates/security/login_user.html.
Author   pallets-eco
Find elsewhere
🌐
Readthedocs
flask-user.readthedocs.io › en › v0.6 › customization.html
Customization — Documentation - Flask-User
# Keep the following Flaks and ... = 'Flask-Security' USER_PASSWORD_HASH = SECURITY_PASSWORD_HASH USER_PASSWORD_SALT = SECURITY_PASSWORD_SALT · The built-in View Functions contain considerable business logic, so we recommend first trying the approach of Form Templates before making use of customized View ...
🌐
GitHub
github.com › pallets-eco › flask-security › issues › 732
Custom LoginForm with single field for Username or Email · Issue #732 · pallets-eco/flask-security
January 21, 2023 - I have an application instance with SECURITY_USERNAME_ENABLE and SECURITY_USERNAME_REQUIRED both True. However, I feel that giving user two input fields for entering username or email is a bit shabby, so I want to give a single input fie...
Author   pallets-eco
🌐
The Teclado Blog
blog.teclado.com › user-authentication-flask-security-too
User authentication in Flask with Flask-Security-Too
May 25, 2026 - Now you can access the login and signup pages that Flask-Security-Too has added to your Flask app for you. For example, going to http://127.0.0.1:5000/login should show you something like this: If you use the menu in that page to go to the "Register" page (or navigate manually to http://127.0.0.1:5000/register) then you'll see a very similar form, which adds a password confirmation field.
🌐
Read the Docs
app.readthedocs.org › projects › ru-flask-security › downloads › pdf › latest pdf
Flask-Security Documentation Выпуск 1.7.4 Matt Wright авг. 28, 2017
• send_login_context_processor: Send login view · Forms · All forms can be overridden. For each form used, you can specify a replacement class. This allows you to · add extra fields to the register form or override validators: 1.9. Customizing Views · 15 · Flask-Security Documentation, Выпуск 1.7.4 ·
🌐
DigitalOcean
digitalocean.com › community › tutorials › how-to-add-authentication-to-your-app-with-flask-login
Add Authentication to Flask Apps with Flask-Login | DigitalOcean
October 22, 2025 - Generate a secure random key using secrets.token_hex(32). Flask blueprints help organize your application into logical modules. You’ll create two blueprints: one for main application routes and another for authentication. Blueprints are Flask’s way of organizing related routes and functionality: main blueprint: Handles public pages (home, profile) auth blueprint: Manages authentication (login, signup, logout)
🌐
Miguel Grinberg
blog.miguelgrinberg.com › post › the-flask-mega-tutorial-part-v-user-logins › page › 10
The Flask Mega-Tutorial, Part V: User Logins - miguelgrinberg.com
February 1, 2018 - Remember those properties that Flask-Login required in the user object? One of those was is_authenticated, which comes in handy to check if the user is logged in or not. When the user is already logged in, I just redirect to the index page. In place of the flash() call that I used earlier, now I can log the user in for real. The first step is to load the user from the database. The username came with the form submission, so I can query the database with that to find the user.
Top answer
1 of 4
20

To login with a username instead of an email address (using Flask-Security 1.7.0 or higher), you can replace the email field with a username field in the User model

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(255), unique=True, index=True)
    password = db.Column(db.String(255))
    active = db.Column(db.Boolean())
    confirmed_at = db.Column(db.DateTime())
    roles = db.relationship('Role', secondary=roles_users,
                            backref=db.backref('users', lazy='dynamic'))

and update the app configuration.

app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = 'username'

Next, to allow users to login using a username instead of an email, we will use the fact that the LoginForm validation method assumes the user identity attribute is in the email form field.

from flask_security.forms import LoginForm
from wtforms import StringField
from wtforms.validators import InputRequired

class ExtendedLoginForm(LoginForm):
    email = StringField('Username', [InputRequired()])

# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore,
                    login_form=ExtendedLoginForm)

This way, we can login using a username without rewriting the validation method or the login template. Of course, this is a hack and the more correct approach would be to add a custom validate method, which checks a username form field, to the ExtendedLoginForm class and to update the login template accordingly.

However, the approach above makes it easy to login with a username or an email address. To do this, define a user model with both a username and email field.

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True)
    username = db.Column(db.String(255), unique=True, index=True)
    password = db.Column(db.String(255))
    active = db.Column(db.Boolean())
    confirmed_at = db.Column(db.DateTime())
    roles = db.relationship('Role', secondary=roles_users,
                            backref=db.backref('users', lazy='dynamic'))

and update the app configuration.

app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ('username','email')

Finally, create the custom login form.

from flask_security.forms import LoginForm
from wtforms import StringField
from wtforms.validators import InputRequired

class ExtendedLoginForm(LoginForm):
    email = StringField('Username or Email Address', [InputRequired()])

# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore,
                    login_form=ExtendedLoginForm)

Now, when logging in, Flask-Security will accept an email or username in the email form field.

2 of 4
3

I managed to implement login using either username or password by overwriting the login form:

class ExtendedLoginForm(LoginForm):
    email = StringField('Username or Email Address')
    username = StringField("Username")

    def validate(self):
    from flask_security.utils import (
        _datastore,
        get_message,
        hash_password,
    )
    from flask_security.confirmable import requires_confirmation
    if not super(LoginForm, self).validate():
        return False

    # try login using email
    self.user = _datastore.get_user(self.email.data)

    if self.user is None:
        self.user = _datastore.get_user(self.username.data)

    if self.user is None:
        self.email.errors.append(get_message("USER_DOES_NOT_EXIST")[0])
        # Reduce timing variation between existing and non-existing users
        hash_password(self.password.data)
        return False
    if not self.user.password:
        self.password.errors.append(get_message("PASSWORD_NOT_SET")[0])
        # Reduce timing variation between existing and non-existing users
        hash_password(self.password.data)
        return False
    if not self.user.verify_and_update_password(self.password.data):
        self.password.errors.append(get_message("INVALID_PASSWORD")[0])
        return False
    if requires_confirmation(self.user):
        self.email.errors.append(get_message("CONFIRMATION_REQUIRED")[0])
        return False
    if not self.user.is_active:
        self.email.errors.append(get_message("DISABLED_ACCOUNT")[0])
        return False
    return True

and register it as described in other posts:

# Setup Flask-Security
app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ('username','email')
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore,
                login_form=ExtendedLoginForm)

Since email and username are optional now one of them can be used to login. But make sure that both fields are set unique in the DB model.

🌐
OneUptime
oneuptime.com › home › blog › how to implement flask-login for authentication
How to Implement Flask-Login for Authentication
February 3, 2026 - sequenceDiagram participant User participant Browser participant Flask participant FlaskLogin participant Database User->>Browser: Submit login form Browser->>Flask: POST /login (credentials) Flask->>Database: Verify credentials Database-->>Flask: ...
🌐
Readthedocs
flask-login.readthedocs.io
Flask-Login 0.7.0 documentation
To do this, use a custom session interface that skips saving the session depending on a flag you set on the request. For example: from flask import g from flask.sessions import SecureCookieSessionInterface from flask_login import user_loaded_from_request @user_loaded_from_request.connect def user_loaded_from_request(app, user=None): g.login_via_request = True class CustomSessionInterface(SecureCookieSessionInterface): """Prevent creating session from API requests.""" def save_session(self, *args, **kwargs): if g.get('login_via_request'): return return super(CustomSessionInterface, self).save_session(*args, **kwargs) app.session_interface = CustomSessionInterface()
🌐
The Teclado Blog
blog.teclado.com › customise-pages-emails-flask-security-too
How to customise pages and emails of Flask-Security-Too
February 20, 2023 - When you add login, signup, or other features with Flask-Security-Too, you get some unstyled plain HTML pages. Learn how to customise and style them in this article.
🌐
Readthedocs
flask-user.readthedocs.io › en › latest › unused.html
Misc — Flask-User v1.0 documentation
# Keep the following Flaks and ... = 'Flask-Security' USER_PASSWORD_HASH = SECURITY_PASSWORD_HASH USER_PASSWORD_SALT = SECURITY_PASSWORD_SALT · The built-in View Functions contain considerable business logic, so we recommend first trying the approach of Form Templates before making use of customized View ...