Recent Posts

Tags

Meta

Understanding Flask-Login Tokens Tutorial

When I was trying to implement Flask-Login into my first Flask application i had difficulty understanding from the flask-login docs how to implement the Authorization Tokens required to use the Remember Me feature. I found another flask plugin package called Flask-Security, which I used to base the below example off of. This example extends the basic example that the Flask-Login Documentation gives and adds support for the get_auth_token and the token_loader methods.

What is what?

Flask is a microframework for Python based on Werkzeug, Jinja 2 and good intentions. Simply Flask is a lightweight framework used for serving dynamic web pages (applications) on the internet. More importantly Flask is Fun. This example uses Flask version 0.9.

Flask-Login is a Python module which helps add user session management for Flask. This example uses Flask-Login version 0.1.3.

itsdangerous is a Python module which helps securely sign cookies in this example. This example uses itsdangerous version 0.17.

Flask-Login Alternative Tokens

In order to implement Flask-Login alternative tokens, which are recommended by Flask-Login you need to implement two methods, get_auth_token in your User Class and token_loader callback method.

get_auth_token

In our user class we need to implement a get_auth_token method which will return a secure token string which will be stored in a cookie on the users computer. The cookie will be used when a user returns to the your site. Flask-Login will load the token and ask us to decode and return a User class with the token_loader function. Because the token is stored on the users computer we need to make it secure. We will use itsdangerous to do that for us. We will combine the username and hashed password into a list then pass that to itsdangerous to encrypt using our flask secret_key.

We store the password hash so that if a user is logged in on multiple computers/browsers and changes their password, it will invalidate the cookie token.

get_auth_token method

Python

1

2

3

4

5

6

7

classUser(UserMixin):

defget_auth_token(self):

"""

Encode a secure token for cookie

"""

data=[str(self.id),self.password]

returnlogin_serializer.dumps(data)

token_loader

The token_loader callback needs to take the token string passed to it and decode it. We also use this method to enforce the expiration date of the token as explained in the code below. Because we stored both the username and password hash in the token, once we decode it we need to check and that the username and password match.

get_auth_token method

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

@login_manager.token_loader

defload_token(token):

"""

Flask-Login token_loader callback.

The token_loader function asks this function to take the token that was

stored on the users computer process it to check if its valid and then

return a User Object if its valid or None if its not valid.

"""

#The Token itself was generated by User.get_auth_token. So it is up to

#us to known the format of the token data itself.

#The Token was encrypted using itsdangerous.URLSafeTimedSerializer which

#allows us to have a max_age on the token itself. When the cookie is stored

#on the users computer it also has a exipry date, but could be changed by

#the user, so this feature allows us to enforce the exipry date of the token

#server side and not rely on the users cookie to exipre.

max_age=app.config["REMEMBER_COOKIE_DURATION"].total_seconds()

#Decrypt the Security Token, data = [username, hashpass]

data=login_serializer.loads(token,max_age=max_age)

#Find the User

user=User.get(data[0])

#Check Password and return user or None

ifuseranddata[1]==user.password:

returnuser

returnNone

Security

Its important to note that the Flask Session Cookie and the Flask-Login Cookie are vulnerable to attack. Although the cookies are encrypted and relatively safe from attack a user who is sniffing network traffic can easily copy the cookies and impersonate the user. The only way to prevent this kind of attack is to use secure sockets (https) when sending back and forth the cookies. This example does not cover that scope.

This example does use a password salt and hash to store the users passwords. It is important to never store a users plain text password, that way if your system is ever compromised someone can still not access users data even with the stored password hash. Wikipedia, readwrite.com

Complete Working Example

Complete flask_login_app.py

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

"""

flask_login_app

==============

An example of how to implement get_auth_token and token_loader of Flask-Login.

This example builds on the excellent docs of flask-login which clearly explains

how to setup basic session based Authentication.

This example uses the python module itsdangerous to handle the encryption and

decryption of the remember me token.

Flask Version: 0.9

Flask-Login Version: 0.1.3

itsdangerous Version: 0.17

Author: Christopher Ross

Site: http://blog.thecircuitnerd.com/flask-login-tokens/

Version: 0.1a

"""

fromdatetimeimporttimedelta

importmd5

fromflask importFlask,request,redirect,render_template

fromflask_login import(LoginManager,login_required,login_user,

current_user,logout_user,UserMixin)

fromitsdangerous importURLSafeTimedSerializer

app=Flask(__name__)

app.debug=True

app.secret_key="a_random_secret_key_$%#!@"

#Login_serializer used to encryt and decrypt the cookie token for the remember

#me option of flask-login

login_serializer=URLSafeTimedSerializer(app.secret_key)

#Flask-Login Login Manager

login_manager=LoginManager()

classUser(UserMixin):

"""

User Class for flask-Login

"""

def__init__(self,userid,password):

self.id=userid

self.password=password

defget_auth_token(self):

"""

Encode a secure token for cookie

"""

data=[str(self.id),self.password]

returnlogin_serializer.dumps(data)

@staticmethod

defget(userid):

"""

Static method to search the database and see if userid exists. If it

does exist then return a User Object. If not then return None as

required by Flask-Login.

"""

#For this example the USERS database is a list consisting of

#(user,hased_password) of users.

foruserinUSERS:

ifuser[0]==userid:

returnUser(user[0],user[1])

returnNone

defhash_pass(password):

"""

Return the md5 hash of the password+salt

"""

salted_password=password+app.secret_key

returnmd5.new(salted_password).hexdigest()

@login_manager.user_loader

defload_user(userid):

"""

Flask-Login user_loader callback.

The user_loader function asks this function to get a User Object or return

None based on the userid.

The userid was stored in the session environment by Flask-Login.

user_loader stores the returned User object in current_user during every

flask request.

"""

returnUser.get(userid)

@login_manager.token_loader

defload_token(token):

"""

Flask-Login token_loader callback.

The token_loader function asks this function to take the token that was

stored on the users computer process it to check if its valid and then

return a User Object if its valid or None if its not valid.

"""

#The Token itself was generated by User.get_auth_token. So it is up to

#us to known the format of the token data itself.

#The Token was encrypted using itsdangerous.URLSafeTimedSerializer which

#allows us to have a max_age on the token itself. When the cookie is stored

#on the users computer it also has a exipry date, but could be changed by

#the user, so this feature allows us to enforce the exipry date of the token

#server side and not rely on the users cookie to exipre.

max_age=app.config["REMEMBER_COOKIE_DURATION"].total_seconds()

#Decrypt the Security Token, data = [username, hashpass]

data=login_serializer.loads(token,max_age=max_age)

#Find the User

user=User.get(data[0])

#Check Password and return user or None

ifuseranddata[1]==user.password:

returnuser

returnNone

@app.route("/logout/")

deflogout_page():

"""

Web Page to Logout User, then Redirect them to Index Page.

"""

logout_user()

returnredirect("/")

@app.route("/login/",methods=["GET","POST"])

deflogin_page():

"""

Web Page to Display Login Form and process form.

"""

ifrequest.method=="POST":

user=User.get(request.form['username'])

#If we found a user based on username then compare that the submitted

#password matches the password in the database. The password is stored

#is a slated hash format, so you must hash the password before comparing

#it.

ifuserandhash_pass(request.form['password'])==user.password:

login_user(user,remember=True)

returnredirect(request.args.get("next")or"/")

returnrender_template("login.html")

@app.route("/")

defindex_page():

"""

Web Page to display The Main Index Page

"""

user_id=(current_user.get_id()or"No User Logged In")

returnrender_template("index.html",user_id=user_id)

@app.route("/restricted/")

@login_required

defrestricted_page():

"""

web page which is restricted and requires the user to be logged in.

"""

#this is just to display the username in the template not required as part

#of any Flask-Login requirements.

user_id=(current_user.get_id()or"No User Logged In")

returnrender_template("restricted.html",user_id=user_id)

if__name__=="__main__":

#Create a quick list of users (username, password). The password is stored

#as a md5 hash that has also been salted. You should never store the users

#password and only store the password after it has been hashed.

USERS=(("user1",hash_pass("pass1")),

("user2",hash_pass("pass2"))

)

#Change the duration of how long the Remember Cookie is valid on the users

#computer. This can not really be trusted as a user can edit it.

app.config["REMEMBER_COOKIE_DURATION"]=timedelta(days=14)

#Tell the login manager where to redirect users to display the login page