The Simple Login/Logout Management for Responder

image1 image2 image3

Powered by Yamato Nagata. Responder-Login provides simple user session management. GithubSimple example

The basic idea is based on Flask-Login

Features

  • Simple login/logout management
  • Provides easy Login_Required/Login_Prohibited decorating.
  • Storing user object in session.

Instllation

Install with pip:

$pip install responder_login

Configuring Application

Before you use Responder_Login, you have to make instance of LoginManager

import responder
from responder_login import LoginManager

api = responder.API()
lm = LoginManager()
lm.init_app(api)  # or you can do lm = LoginManager(api)

How It Works

To make this work, You have to provide LoginManager.user_loader callback. callback is used to specify user object with the data which stored in the session. This should take unicode and return the instance of user object. If no object found, please return None. Then, the instance of AnonymousUserMixin object will be an returned.

@lm.user_loader
def load_user(user_id):
   return User.get(user_id)

Custom User Object

This section is quoted from Flask-Login documentation The class that you use to represent users needs to implement these properties and methods:

is_authenticated
This property should return True if the user is authenticated, i.e. they have provided valid credentials. (Only authenticated users will fulfill the criteria of login_required.)
is_active
This property should return True if this is an active user - in addition to being authenticated, they also have activated their account, not been suspended, or any condition your application has for rejecting an account. Inactive accounts may not log in (without being forced of course).
is_anonymous
This property should return True if this is an anonymous user. (Actual users should return False instead.)
get_id()
This method must return a str that uniquely identifies this user, and can be used to load the user from the LoginManager.user_loader callback. Note that this must be a unicode - if the ID is natively an int or some other type, you will need to convert it to str.

To make implementing a user class easier, you can inherit from UserMixin, which provides default implementations for all of these properties and methods. (It’s not required, though.)

Login Example

We can use LoginManager.login_user to log users in.

for example.

@api.route("/login")
async def login(req, resp):
    if req.method == "get":
        # this returns the form let the users
        # input and send server some data to specify User Object
        resp.html = """
        Login<br>
        <form action="/login" method="post">
        <input name="name"><label>name</label></input><br>
        <input name="age"><label>age</label></input><br>
        <button type="submit">submit</button>
        </form>"""
    else:
        data = await req.media(format="form")
        user = UserObject(name=data["name"], age=data["age"])
        # We made an UserObject from user's data
        lm.login_user(user)
        # this make users log in.
        resp.html = f"""
        you are now logging in as <br>
        name: {user.name},
        age: {user.age}"""

and then, LoginManager.logout_user() to log out. like this

@api.route("/logout")
def logout(req, resp):
    user = lm.current_user
    lm.logout_user()
    resp.html = f"""logged out the user.<br>
     name: {user.name}<br>
     age: {user.age}"""

But in case if user isn’t logged in, above code will raise AttributeError when find name or age in of user. So, LoginManager provides LoginManager.login_required decorator. like below.

@api.route("/logout")
@lm.login_required
def logout(req, resp):
    user = lm.current_user
    lm.logout_user()
    resp.html = f"""logged out the user.<br>
     name: {user.name}<br>
     age: {user.age}"""

and, If you want to make some page you don’t want logged in user to get in, you can set LoginManager.login_prohibited decorator.

Customizing

LoginManager(api=None)

Initialize LoginManager.:code:api must be an instance of Responder.API. api can be None or not provided. But, you have to LoginManager.init_api()

LoginManager.init_api(api)

Set Responder.API with given api.

@LoginManager.user_loader

This decorates callback to set it LoginManager._user_callback callback must take one argument and return instance of User object or None.

@LoginManager.login_required

The decorator which decorates Responder.route callback. If user want to access decorated route but he/her must log in, this will call LoginManager._unauthorized_callback if it’s provided. That can be set by decorating callback with LoginManager.unauthorized_handler which takes Request and Response. If LoginManager._unauthorized_callback isn’t provided, this will redirect to LoginManager.config["LOGIN_REQUIRED_ROUTE"] if it’s set. If not, return LoginManager.config["LOGIN_REQUIRED_MESSAGE"]

@LoginManager.unauthorized_handler

This decorates callback to set it LoginManager._unauthorized_callback

@LoginManager.login_prohibited

The decorator which decorates Responder.route callback. If user want to access decorated route but he/her must log out, this will call LoginManager._authorized_callback if it’s provided. That can be set by decorating callback with LoginManager.authorized_handler which takes Request and Response. If LoginManager._authorized_callback isn’t provided, this will redirect to LoginManager.config["LOGIN_PROHIBITED_ROUTE"] if it’s set. If not, return LoginManager.config["LOGIN_PROHIBITED_MESSAGE"]

@LoginManager.authorized_handler

This decorates callback to set it LoginManager._authorized_callback

LoginManager.login_user(user)

user must be an instance of user object. Set cookies with Response.set_cookie()

about account data, each value is below:

  • key : LoginManager.config[COOKIE_NAME]["ACCOUNT"]
  • value : user.get_id()
  • expires : if LoginManager.config["COOKIE_REMEMBER_ME"]["ACCOUNT"] (Defaults to True), True, datetime.datetime.now() + LoginManager.config["COOKIE_DURATION"]. Otherwise, None
  • max_age : if LoginManager.config["COOKIE_REMEMBER_ME"]["ACCOUNT"] (Defaults to True), True, LoginManager.config["COOKIE_DURATION"].total_seconds(). Otherwise, None
  • secure : LoginManager.config["COOKIE_SECURE"] Defaults to False
  • httponly : LoginManager.config["COOKIE_HTTPONLY"] Defaults to False
about is_fresh:
  • key : LoginManager.config[COOKIE_NAME]["IS_FRESH"]
  • value : 1
  • expires : if LoginManager.config["COOKIE_REMEMBER_ME"]["IS_FRESH"] (Defaults to False), True, datetime.datetime.now() + LoginManager.config["COOKIE_DURATION"]. Otherwise, None
  • max_age : if LoginManager.config["COOKIE_REMEMBER_ME"]["IS_FRESH"] (Defaults to True), True, LoginManager.config["COOKIE_DURATION"].total_seconds(). Otherwise, none
  • secure : LoginManager.config["COOKIE_SECURE"] Defaults to False
  • httponly : LoginManager.config["COOKIE_HTTPONLY"] Defaults to False

LoginManager.logout_user()

This log users out by setting cookie that expires and max_age are 0

LoginManager.current_user

This returns instance of user object by searching instance by LoginManager._user_callback. If no user found (callback returned None), this will return LoginManager.anonumous_user

LoginManager.is_fresh

Return If the user logging in is logged in current session(not using Remember me). If logged in current session, returns True. If not, False. This returns False if user is not logged in.

LoginManager.config

  • COOKIE_NAME : The dictionary of cookie name. keys are`”ACCOUNT”` and "IS_FRESH". Defaults to {"ACCOUNT": "account", "IS_FRESH": "fresh" }
  • COOKIE_REMEMBER_ME : The dictionary of setting whether each cookie is Remember-me. keys are`”ACCOUNT”` and "IS_FRESH". Defaults to {"ACCOUNT": True, "IS_FRESH": False }
  • COOKIE_DURATION : The amount of time before the cookie expires, as a datetime.timedelta object. Defaults to datetime.timedelta(60)
  • COOKIE_SECURE : Restricts the “Remember Me” cookie’s scope to https. Defaults to False
  • COOKIE_HTTPONLY : Prevents the “Remember Me” cookie from being accessed by client-side scripts. Defaults to False
  • LOGIN_REQUIRED_ROUTE : The route redirect to if user is not logged in and tried to access endpoint decorated with LoginManager.login_required. Defaults to None
  • LOGIN_REQUIRED_MESSAGE : The default message to display when users need to log in. Defaults to "Please log in to access this page."
  • LOGIN_PROHIBITED_ROUTE : The route redirect to if user is logged in and tried to access endpoint decorated with LoginManager.login_prohibited. Defaults to None
  • LOGIN_PROHIBITED_MESSAGE : The default message to display when users need to log out. Defaults to "Please log out to access this page."

UserMixin

The simple mixin to make user object.

class UserMixin:

    @property
    def is_active(self):
        return True

    @property
    def is_authenticated(self):
        return True

    @property
    def is_anonymous(self):
        return False

    def get_id(self):
        try:
            return self.id
        except AttributeError:
            raise NotImplementedError('No `id` attribute. override `get_id` or set `id` attribute')

    def __eq__(self, other):
        if isinstance(other, UserMixin):
            return self.get_id() == other.get_id()
        return NotImplemented

    def __ne__(self, other):
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return NotImplemented
        return not equal

AnonymousUserMixin

The user mixin for not logged in users

class AnonymousUserMixin(UserMixin):
    @property
    def is_authenticated(self):
        return False

    @property
    def is_active(self):
        return False

    @property
    def is_anonymous(self):
        return True

    def get_id(self):
        return None

Indices and tables

In End

Sorry for my poor English. I want you to join us and send many pull requests about Doc, code, features and more!!