The project is about implementing a session authentication mechanism without installing any other module. The learning objectives of the project include;
- Understanding authentication, session authentication.
- Cookies, sending cookies, and parsing cookies.
Score: 0.0% (Checks completed: 0.0%)
Copy all your work of the 0x06. Basic authentication project in this new folder.
In this version, you implemented a Basic authentication for giving you access to all User endpoints:
GET /api/v1/users
POST /api/v1/users
GET /api/v1/users/<user_id>
PUT /api/v1/users/<user_id>
DELETE /api/v1/users/<user_id>
Now, you will add a new endpoint: GET /users/me
to retrieve the authenticated User
object.
- Copy folders
models
andapi
from the previous project0x06. Basic authentication
- Please make sure all mandatory tasks of this previous project are done at 100% because this project (and the rest of this track) will be based on it.
- Update
@app.before_request
inapi/v1/app.py
:- Assign the result of
auth.current_user(request)
torequest.current_user
- Assign the result of
- Update method for the route
GET /api/v1/users/<user_id>
inapi/v1/views/users.py
:- If
<user_id>
is equal tome
andrequest.current_user
isNone
:abort(404)
- If
<user_id>
is equal tome
andrequest.current_user
is notNone
: return the authenticatedUser
in a JSON response (like a normal case ofGET /api/v1/users/<user_id>
where<user_id>
is a validUser
ID) - Otherwise, keep the same behavior
- If
In the first terminal:
bob@dylan:~$ cat main_0.py
#!/usr/bin/env python3
""" Main 0
"""
import base64
from api.v1.auth.basic_auth import BasicAuth
from models.user import User
""" Create a user test """
user_email = "[email protected]"
user_clear_pwd = "H0lbertonSchool98!"
user = User()
user.email = user_email
user.password = user_clear_pwd
print("New user: {}".format(user.id))
user.save()
basic_clear = "{}:{}".format(user_email, user_clear_pwd)
print("Basic Base64: {}".format(base64.b64encode(basic_clear.encode('utf-8')).decode("utf-8")))
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=basic_auth ./main_0.py
New user: 9375973a-68c7-46aa-b135-29f79e837495
Basic Base64: Ym9iQGhidG4uaW86SDBsYmVydG9uU2Nob29sOTgh
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=basic_auth python3 -m api.v1.app
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
....
In a second terminal:
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/status"
{
"status": "OK"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users"
{
"error": "Unauthorized"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users" -H "Authorization: Basic Ym9iQGhidG4uaW86SDBsYmVydG9uU2Nob29sOTgh"
[
{
"created_at": "2017-09-25 01:55:17",
"email": "[email protected]",
"first_name": null,
"id": "9375973a-68c7-46aa-b135-29f79e837495",
"last_name": null,
"updated_at": "2017-09-25 01:55:17"
}
]
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users/me" -H "Authorization: Basic Ym9iQGhidG4uaW86SDBsYmVydG9uU2Nob29sOTgh"
{
"created_at": "2017-09-25 01:55:17",
"email": "[email protected]",
"first_name": null,
"id": "9375973a-68c7-46aa-b135-29f79e837495",
"last_name": null,
"updated_at": "2017-09-25 01:55:17"
}
bob@dylan:~$
Score: 0.0% (Checks completed: 0.0%)
Create a class SessionAuth
that inherits from Auth
. For the moment this class will be empty. It’s the first step for creating a new authentication mechanism:
- validate if everything inherits correctly without any overloading
- validate the “switch” by using environment variables
Update api/v1/app.py
for using SessionAuth
instance for the variable auth
depending of the value of the environment variable AUTH_TYPE
, If AUTH_TYPE
is equal to session_auth
:
- import
SessionAuth
fromapi.v1.auth.session_auth
- create an instance of
SessionAuth
and assign it to the variableauth
Otherwise, keep the previous mechanism.
In the first terminal:
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=session_auth python3 -m api.v1.app
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
....
In a second terminal:
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/status"
{
"status": "OK"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/status/"
{
"status": "OK"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users"
{
"error": "Unauthorized"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users" -H "Authorization: Test"
{
"error": "Forbidden"
}
bob@dylan:~$
# Start server.
API_HOST=0.0.0.0 API_PORT=5000 python3 -m api.v1.app
# Tests.
curl "http://0.0.0.0:5000/api/v1/unauthorized"
Score: 0.0% (Checks completed: 0.0%)
Update SessionAuth
class:
- Create a class attribute
user_id_by_session_id
initialized by an empty dictionary - Create an instance method
def create_session(self, user_id: str = None) -> str:
that creates a Session ID for auser_id
:- Return
None
ifuser_id
isNone
- Return
None
ifuser_id
is not a string - Otherwise:
- Generate a Session ID using
uuid
module anduuid4()
likeid
inBase
- Use this Session ID as key of the dictionary
user_id_by_session_id
- the value for this key must beuser_id
- Return the Session ID
- Generate a Session ID using
- The same
user_id
can have multiple Session ID - indeed, theuser_id
is the value in the dictionaryuser_id_by_session_id
- Return
Now you an “in-memory” Session ID storing. You will be able to retrieve an User
id based on a Session ID.
bob@dylan:~$ cat main_1.py
#!/usr/bin/env python3
""" Main 1
"""
from api.v1.auth.session_auth import SessionAuth
sa = SessionAuth()
print("{}: {}".format(type(sa.user_id_by_session_id), sa.user_id_by_session_id))
user_id = None
session = sa.create_session(user_id)
print("{} => {}: {}".format(user_id, session, sa.user_id_by_session_id))
user_id = 89
session = sa.create_session(user_id)
print("{} => {}: {}".format(user_id, session, sa.user_id_by_session_id))
user_id = "abcde"
session = sa.create_session(user_id)
print("{} => {}: {}".format(user_id, session, sa.user_id_by_session_id))
user_id = "fghij"
session = sa.create_session(user_id)
print("{} => {}: {}".format(user_id, session, sa.user_id_by_session_id))
user_id = "abcde"
session = sa.create_session(user_id)
print("{} => {}: {}".format(user_id, session, sa.user_id_by_session_id))
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=session_auth ./main_1.py
<class 'dict'>: {}
None => None: {}
89 => None: {}
abcde => 61997a1b-3f8a-4b0f-87f6-19d5cafee63f: {'61997a1b-3f8a-4b0f-87f6-19d5cafee63f': 'abcde'}
fghij => 69e45c25-ec89-4563-86ab-bc192dcc3b4f: {'61997a1b-3f8a-4b0f-87f6-19d5cafee63f': 'abcde', '69e45c25-ec89-4563-86ab-bc192dcc3b4f': 'fghij'}
abcde => 02079cb4-6847-48aa-924e-0514d82a43f4: {'61997a1b-3f8a-4b0f-87f6-19d5cafee63f': 'abcde', '02079cb4-6847-48aa-924e-0514d82a43f4': 'abcde', '69e45c25-ec89-4563-86ab-bc192dcc3b4f': 'fghij'}
bob@dylan:~$
Score: 0.0% (Checks completed: 0.0%)
Update SessionAuth
class:
Create an instance method def user_id_for_session_id(self, session_id: str = None) -> str:
that returns a User
ID based on a Session ID:
- Return
None
ifsession_id
isNone
- Return
None
ifsession_id
is not a string - Return the value (the User ID) for the key
session_id
in the dictionaryuser_id_by_session_id
. - You must use
.get()
built-in for accessing in a dictionary a value based on key
Now you have 2 methods (create_session
and user_id_for_session_id
) for storing and retrieving a link between a User
ID and a Session ID.
bob@dylan:~$ cat main_2.py
#!/usr/bin/env python3
""" Main 2
"""
from api.v1.auth.session_auth import SessionAuth
sa = SessionAuth()
user_id_1 = "abcde"
session_1 = sa.create_session(user_id_1)
print("{} => {}: {}".format(user_id_1, session_1, sa.user_id_by_session_id))
user_id_2 = "fghij"
session_2 = sa.create_session(user_id_2)
print("{} => {}: {}".format(user_id_2, session_2, sa.user_id_by_session_id))
print("---")
tmp_session_id = None
tmp_user_id = sa.user_id_for_session_id(tmp_session_id)
print("{} => {}".format(tmp_session_id, tmp_user_id))
tmp_session_id = 89
tmp_user_id = sa.user_id_for_session_id(tmp_session_id)
print("{} => {}".format(tmp_session_id, tmp_user_id))
tmp_session_id = "doesntexist"
tmp_user_id = sa.user_id_for_session_id(tmp_session_id)
print("{} => {}".format(tmp_session_id, tmp_user_id))
print("---")
tmp_session_id = session_1
tmp_user_id = sa.user_id_for_session_id(tmp_session_id)
print("{} => {}".format(tmp_session_id, tmp_user_id))
tmp_session_id = session_2
tmp_user_id = sa.user_id_for_session_id(tmp_session_id)
print("{} => {}".format(tmp_session_id, tmp_user_id))
print("---")
session_1_bis = sa.create_session(user_id_1)
print("{} => {}: {}".format(user_id_1, session_1_bis, sa.user_id_by_session_id))
tmp_user_id = sa.user_id_for_session_id(session_1_bis)
print("{} => {}".format(session_1_bis, tmp_user_id))
tmp_user_id = sa.user_id_for_session_id(session_1)
print("{} => {}".format(session_1, tmp_user_id))
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=session_auth ./main_2.py
abcde => 8647f981-f503-4638-af23-7bb4a9e4b53f: {'8647f981-f503-4638-af23-7bb4a9e4b53f': 'abcde'}
fghij => a159ee3f-214e-4e91-9546-ca3ce873e975: {'a159ee3f-214e-4e91-9546-ca3ce873e975': 'fghij', '8647f981-f503-4638-af23-7bb4a9e4b53f': 'abcde'}
---
None => None
89 => None
doesntexist => None
---
8647f981-f503-4638-af23-7bb4a9e4b53f => abcde
a159ee3f-214e-4e91-9546-ca3ce873e975 => fghij
---
abcde => 5d2930ba-f6d6-4a23-83d2-4f0abc8b8eee: {'a159ee3f-214e-4e91-9546-ca3ce873e975': 'fghij', '8647f981-f503-4638-af23-7bb4a9e4b53f': 'abcde', '5d2930ba-f6d6-4a23-83d2-4f0abc8b8eee': 'abcde'}
5d2930ba-f6d6-4a23-83d2-4f0abc8b8eee => abcde
8647f981-f503-4638-af23-7bb4a9e4b53f => abcde
bob@dylan:~$
[:point_right: api/v1/auth/session_auth.py](api/v1/auth/session_auth.py
Score: 0.0% (Checks completed: 0.0%)
Update api/v1/auth/auth.py
by adding the method def session_cookie(self, request=None):
that returns a cookie value from a request:
- Return
None
ifrequest
isNone
- Return the value of the cookie named
_my_session_id
fromrequest
- the name of the cookie must be defined by the environment variableSESSION_NAME
- You must use
.get()
built-in for accessing the cookie in the request cookies dictionary - You must use the environment variable
SESSION_NAME
to define the name of the cookie used for the Session ID
In the first terminal:
bob@dylan:~$ cat main_3.py
#!/usr/bin/env python3
""" Cookie server
"""
from flask import Flask, request
from api.v1.auth.auth import Auth
auth = Auth()
app = Flask(__name__)
@app.route('/', methods=['GET'], strict_slashes=False)
def root_path():
""" Root path
"""
return "Cookie value: {}\n".format(auth.session_cookie(request))
if __name__ == "__main__":
app.run(host="0.0.0.0", port="5000")
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=session_auth SESSION_NAME=_my_session_id ./main_3.py
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
....
In a second terminal:
bob@dylan:~$ curl "http://0.0.0.0:5000"
Cookie value: None
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000" --cookie "_my_session_id=Hello"
Cookie value: Hello
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000" --cookie "_my_session_id=C is fun"
Cookie value: C is fun
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000" --cookie "_my_session_id_fake"
Cookie value: None
bob@dylan:~$
Score: 0.0% (Checks completed: 0.0%)
Update the @app.before_request
method in api/v1/app.py
:
- Add the URL path
/api/v1/auth_session/login/
in the list of excluded paths of the methodrequire_auth
- this route doesn’t exist yet but it should be accessible outside authentication - If
auth.authorization_header(request)
andauth.session_cookie(request)
returnNone
,abort(401)
In the first terminal:
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=session_auth SESSION_NAME=_my_session_id python3 -m api.v1.app
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
....
In a second terminal:
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/status"
{
"status": "OK"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/auth_session/login" # not found but not "blocked" by an authentication system
{
"error": "Not found"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users/me"
{
"error": "Unauthorized"
}
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users/me" -H "Authorization: Basic Ym9iQGhidG4uaW86SDBsYmVydG9uU2Nob29sOTgh" # Won't work because the environment variable AUTH_TYPE is equal to "session_auth"
{
"error": "Forbidden"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users/me" --cookie "_my_session_id=5535d4d7-3d77-4d06-8281-495dc3acfe76" # Won't work because no user is linked to this Session ID
{
"error": "Forbidden"
}
bob@dylan:~$
Score: 0.0% (Checks completed: 0.0%)
Update SessionAuth
class:
Create an instance method def current_user(self, request=None):
(overload) that returns a User
instance based on a cookie value:
- You must use
self.session_cookie(...)
andself.user_id_for_session_id(...)
to return the User ID based on the cookie_my_session_id
- By using this User ID, you will be able to retrieve a
User
instance from the database - you can useUser.get(...)
for retrieving aUser
from the database.
Now, you will be able to get a User based on his session ID.
In the first terminal:
bob@dylan:~$ cat main_4.py
#!/usr/bin/env python3
""" Main 4
"""
from flask import Flask, request
from api.v1.auth.session_auth import SessionAuth
from models.user import User
""" Create a user test """
user_email = "[email protected]"
user_clear_pwd = "fake pwd"
user = User()
user.email = user_email
user.password = user_clear_pwd
user.save()
""" Create a session ID """
sa = SessionAuth()
session_id = sa.create_session(user.id)
print("User with ID: {} has a Session ID: {}".format(user.id, session_id))
""" Create a Flask app """
app = Flask(__name__)
@app.route('/', methods=['GET'], strict_slashes=False)
def root_path():
""" Root path
"""
request_user = sa.current_user(request)
if request_user is None:
return "No user found\n"
return "User found: {}\n".format(request_user.id)
if __name__ == "__main__":
app.run(host="0.0.0.0", port="5000")
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=session_auth SESSION_NAME=_my_session_id ./main_4.py
User with ID: cf3ddee1-ff24-49e4-a40b-2540333fe992 has a Session ID: 9d1648aa-da79-4692-8236-5f9d7f9e9485
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
....
In a second terminal:
bob@dylan:~$ curl "http://0.0.0.0:5000/"
No user found
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/" --cookie "_my_session_id=Holberton"
No user found
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/" --cookie "_my_session_id=9d1648aa-da79-4692-8236-5f9d7f9e9485"
User found: cf3ddee1-ff24-49e4-a40b-2540333fe992
bob@dylan:~$
Score: 0.0% (Checks completed: 0.0%)
Create a new Flask view that handles all routes for the Session authentication.
In the file api/v1/views/session_auth.py
, create a route POST /auth_session/login
(= POST /api/v1/auth_session/login
):
- Slash tolerant (
/auth_session/login
==/auth_session/login/
) - You must use
request.form.get()
to retrieveemail
andpassword
parameters - If
email
is missing or empty, return the JSON{ "error": "email missing" }
with the status code400
- If
password
is missing or empty, return the JSON{ "error": "password missing" }
with the status code400
- Retrieve the
User
instance based on theemail
- you must use the class methodsearch
ofUser
(same as the one used for theBasicAuth
)- If no
User
found, return the JSON{ "error": "no user found for this email" }
with the status code404
- If the
password
is not the one of theUser
found, return the JSON{ "error": "wrong password" }
with the status code401
- you must useis_valid_password
from theUser
instance - Otherwise, create a Session ID for the
User
ID:- You must use
from api.v1.app import auth
- WARNING: please import it only where you need it - not on top of the file (can generate circular import - and break first tasks of this project) - You must use
auth.create_session(..)
for creating a Session ID - Return the dictionary representation of the
User
- you must useto_json()
method from User - You must set the cookie to the response - you must use the value of the environment variable
SESSION_NAME
as cookie name - tip
- You must use
- If no
In the file api/v1/views/__init__.py
, you must add this new view at the end of the file.
In the first terminal:
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=session_auth SESSION_NAME=_my_session_id python3 -m api.v1.app
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
....
In a second terminal:
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/auth_session/login" -XGET
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/auth_session/login" -XPOST
{
"error": "email missing"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/auth_session/login" -XPOST -d "[email protected]"
{
"error": "password missing"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/auth_session/login" -XPOST -d "[email protected]" -d "password=test"
{
"error": "no user found for this email"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/auth_session/login" -XPOST -d "[email protected]" -d "password=test"
{
"error": "wrong password"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/auth_session/login" -XPOST -d "[email protected]" -d "password=fake pwd"
{
"created_at": "2017-10-16 04:23:04",
"email": "[email protected]",
"first_name": null,
"id": "cf3ddee1-ff24-49e4-a40b-2540333fe992",
"last_name": null,
"updated_at": "2017-10-16 04:23:04"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/auth_session/login" -XPOST -d "[email protected]" -d "password=fake pwd" -vvv
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 0.0.0.0...
* TCP_NODELAY set
* Connected to 0.0.0.0 (127.0.0.1) port 5000 (#0)
> POST /api/v1/auth_session/login HTTP/1.1
> Host: 0.0.0.0:5000
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Length: 42
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 42 out of 42 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: application/json
< Set-Cookie: _my_session_id=df05b4e1-d117-444c-a0cc-ba0d167889c4; Path=/
< Access-Control-Allow-Origin: *
< Content-Length: 210
< Server: Werkzeug/0.12.1 Python/3.4.3
< Date: Mon, 16 Oct 2017 04:57:08 GMT
<
{
"created_at": "2017-10-16 04:23:04",
"email": "[email protected]",
"first_name": null,
"id": "cf3ddee1-ff24-49e4-a40b-2540333fe992",
"last_name": null,
"updated_at": "2017-10-16 04:23:04"
}
* Closing connection 0
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users/me" --cookie "_my_session_id=df05b4e1-d117-444c-a0cc-ba0d167889c4"
{
"created_at": "2017-10-16 04:23:04",
"email": "[email protected]",
"first_name": null,
"id": "cf3ddee1-ff24-49e4-a40b-2540333fe992",
"last_name": null,
"updated_at": "2017-10-16 04:23:04"
}
bob@dylan:~$
Now you have an authentication based on a Session ID stored in cookie, perfect for a website (browsers love cookies).
Score: 0.0% (Checks completed: 0.0%)
Update the class SessionAuth
by adding a new method def destroy_session(self, request=None):
that deletes the user session / logout:
- If the
request
is equal toNone
, returnFalse
- If the
request
doesn’t contain the Session ID cookie, returnFalse
- you must useself.session_cookie(request)
- If the Session ID of the request is not linked to any User ID, return
False
- you must useself.user_id_for_session_id(...)
- Otherwise, delete in
self.user_id_by_session_id
the Session ID (as key of this dictionary) and returnTrue
Update the file api/v1/views/session_auth.py
, by adding a new route DELETE /api/v1/auth_session/logout
:
- Slash tolerant
- You must use
from api.v1.app import auth
- You must use
auth.destroy_session(request)
for deleting the Session ID contains in the request as cookie:- If
destroy_session
returnsFalse
,abort(404)
- Otherwise, return an empty JSON dictionary with the status code 200
- If
In the first terminal:
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=session_auth SESSION_NAME=_my_session_id python3 -m api.v1.app
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
....
In a second terminal:
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/auth_session/login" -XPOST -d "[email protected]" -d "password=fake pwd" -vvv
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 0.0.0.0...
* TCP_NODELAY set
* Connected to 0.0.0.0 (127.0.0.1) port 5000 (#0)
> POST /api/v1/auth_session/login HTTP/1.1
> Host: 0.0.0.0:5000
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Length: 42
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 42 out of 42 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: application/json
< Set-Cookie: _my_session_id=e173cb79-d3fc-4e3a-9e6f-bcd345b24721; Path=/
< Access-Control-Allow-Origin: *
< Content-Length: 210
< Server: Werkzeug/0.12.1 Python/3.4.3
< Date: Mon, 16 Oct 2017 04:57:08 GMT
<
{
"created_at": "2017-10-16 04:23:04",
"email": "[email protected]",
"first_name": null,
"id": "cf3ddee1-ff24-49e4-a40b-2540333fe992",
"last_name": null,
"updated_at": "2017-10-16 04:23:04"
}
* Closing connection 0
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users/me" --cookie "_my_session_id=e173cb79-d3fc-4e3a-9e6f-bcd345b24721"
{
"created_at": "2017-10-16 04:23:04",
"email": "[email protected]",
"first_name": null,
"id": "cf3ddee1-ff24-49e4-a40b-2540333fe992",
"last_name": null,
"updated_at": "2017-10-16 04:23:04"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/auth_session/logout" --cookie "_my_session_id=e173cb79-d3fc-4e3a-9e6f-bcd345b24721"
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/auth_session/logout" --cookie "_my_session_id=e173cb79-d3fc-4e3a-9e6f-bcd345b24721" -XDELETE
{}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users/me" --cookie "_my_session_id=e173cb79-d3fc-4e3a-9e6f-bcd345b24721"
{
"error": "Forbidden"
}
bob@dylan:~$
Login, logout… what’s else?
Now, after getting a Session ID, you can request all protected API routes by using this Session ID, no need anymore to send User email and password every time.
Score: 0.0% (Checks completed: 0.0%)
Actually you have 2 authentication systems:
- Basic authentication
- Session authentication
Now you will add an expiration date to a Session ID.
Create a class SessionExpAuth
that inherits from SessionAuth
in the file api/v1/auth/session_exp_auth.py
:
- Overload
def __init__(self):
method:- Assign an instance attribute
session_duration
:- To the environment variable
SESSION_DURATION
casts to an integer - If this environment variable doesn’t exist or can’t be parse to an integer, assign to 0
- To the environment variable
- Assign an instance attribute
- Overload
def create_session(self, user_id=None):
- Create a Session ID by calling
super()
-super()
will call thecreate_session()
method ofSessionAuth
- Return
None
ifsuper()
can’t create a Session ID - Use this Session ID as key of the dictionary
user_id_by_session_id
- the value for this key must be a dictionary (called “session dictionary”):- The key
user_id
must be set to the variableuser_id
- The key
created_at
must be set to the current datetime - you must usedatetime.now()
- The key
- Return the Session ID created
- Create a Session ID by calling
- Overload
def user_id_for_session_id(self, session_id=None):
- Return
None
ifsession_id
isNone
- Return
None
ifuser_id_by_session_id
doesn’t contain any key equals tosession_id
- Return the
user_id
key from the session dictionary ifself.session_duration
is equal or under 0 - Return
None
if session dictionary doesn’t contain a keycreated_at
- Return
None
if thecreated_at
+session_duration
seconds are before the current datetime. datetime - timedelta - Otherwise, return
user_id
from the session dictionary
- Return
Update api/v1/app.py
to instantiate auth with SessionExpAuth
if the environment variable AUTH_TYPE
is equal to session_exp_auth
.
In the first terminal:
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=session_exp_auth SESSION_NAME=_my_session_id SESSION_DURATION=60 python3 -m api.v1.app
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
....
In a second terminal:
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/auth_session/login" -XPOST -d "[email protected]" -d "password=fake pwd" -vvv
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 0.0.0.0...
* TCP_NODELAY set
* Connected to 0.0.0.0 (127.0.0.1) port 5000 (#0)
> POST /api/v1/auth_session/login HTTP/1.1
> Host: 0.0.0.0:5000
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Length: 42
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 42 out of 42 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: application/json
< Set-Cookie: _my_session_id=eea5d963-8dd2-46f0-9e43-fd05029ae63f; Path=/
< Access-Control-Allow-Origin: *
< Content-Length: 210
< Server: Werkzeug/0.12.1 Python/3.4.3
< Date: Mon, 16 Oct 2017 04:57:08 GMT
<
{
"created_at": "2017-10-16 04:23:04",
"email": "[email protected]",
"first_name": null,
"id": "cf3ddee1-ff24-49e4-a40b-2540333fe992",
"last_name": null,
"updated_at": "2017-10-16 04:23:04"
}
* Closing connection 0
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users/me" --cookie "_my_session_id=eea5d963-8dd2-46f0-9e43-fd05029ae63f"
{
"created_at": "2017-10-16 04:23:04",
"email": "[email protected]",
"first_name": null,
"id": "cf3ddee1-ff24-49e4-a40b-2540333fe992",
"last_name": null,
"updated_at": "2017-10-16 04:23:04"
}
bob@dylan:~$
bob@dylan:~$ sleep 10
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users/me" --cookie "_my_session_id=eea5d963-8dd2-46f0-9e43-fd05029ae63f"
{
"created_at": "2017-10-16 04:23:04",
"email": "[email protected]",
"first_name": null,
"id": "cf3ddee1-ff24-49e4-a40b-2540333fe992",
"last_name": null,
"updated_at": "2017-10-16 04:23:04"
}
bob@dylan:~$
bob@dylan:~$ sleep 51 # 10 + 51 > 60
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users/me" --cookie "_my_session_id=eea5d963-8dd2-46f0-9e43-fd05029ae63f"
{
"error": "Forbidden"
}
bob@dylan:~$
Score: 0.0% (Checks completed: 0.0%)
Since the beginning, all Session IDs are stored in memory. It means, if your application stops, all Session IDs are lost.
For avoid that, you will create a new authentication system, based on Session ID stored in database (for us, it will be in a file, like User
).
Create a new model UserSession
in models/user_session.py
that inherits from Base
:
- Implement the
def __init__(self, *args: list, **kwargs: dict):
like inUser
but for these 2 attributes:user_id
: stringsession_id
: string
Create a new authentication class SessionDBAuth
in api/v1/auth/session_db_auth.py
that inherits from SessionExpAuth
:
- Overload
def create_session(self, user_id=None):
that creates and stores new instance ofUserSession
and returns the Session ID - Overload
def user_id_for_session_id(self, session_id=None):
that returns the User ID by requestingUserSession
in the database based onsession_id
- Overload
def destroy_session(self, request=None):
that destroys theUserSession
based on the Session ID from the request cookie
Update api/v1/app.py
to instantiate auth
with SessionDBAuth
if the environment variable AUTH_TYPE
is equal to session_db_auth
.
In the first terminal:
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=session_db_auth SESSION_NAME=_my_session_id SESSION_DURATION=60 python3 -m api.v1.app
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
....
In a second terminal:
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/auth_session/login" -XPOST -d "[email protected]" -d "password=fake pwd" -vvv
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 0.0.0.0...
* TCP_NODELAY set
* Connected to 0.0.0.0 (127.0.0.1) port 5000 (#0)
> POST /api/v1/auth_session/login HTTP/1.1
> Host: 0.0.0.0:5000
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Length: 42
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 42 out of 42 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: application/json
< Set-Cookie: _my_session_id=bacadfad-3c3b-4830-b1b2-3d77dfb9ad13; Path=/
< Access-Control-Allow-Origin: *
< Content-Length: 210
< Server: Werkzeug/0.12.1 Python/3.4.3
< Date: Mon, 16 Oct 2017 04:57:08 GMT
<
{
"created_at": "2017-10-16 04:23:04",
"email": "[email protected]",
"first_name": null,
"id": "cf3ddee1-ff24-49e4-a40b-2540333fe992",
"last_name": null,
"updated_at": "2017-10-16 04:23:04"
}
* Closing connection 0
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users/me" --cookie "_my_session_id=bacadfad-3c3b-4830-b1b2-3d77dfb9ad13"
{
"created_at": "2017-10-16 04:23:04",
"email": "[email protected]",
"first_name": null,
"id": "cf3ddee1-ff24-49e4-a40b-2540333fe992",
"last_name": null,
"updated_at": "2017-10-16 04:23:04"
}
bob@dylan:~$
bob@dylan:~$ sleep 10
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users/me" --cookie "_my_session_id=bacadfad-3c3b-4830-b1b2-3d77dfb9ad13"
{
"created_at": "2017-10-16 04:23:04",
"email": "[email protected]",
"first_name": null,
"id": "cf3ddee1-ff24-49e4-a40b-2540333fe992",
"last_name": null,
"updated_at": "2017-10-16 04:23:04"
}
bob@dylan:~$
bob@dylan:~$ sleep 60
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users/me" --cookie "_my_session_id=bacadfad-3c3b-4830-b1b2-3d77dfb9ad13"
{
"error": "Forbidden"
}
bob@dylan:~$
[:point_right: 👉 api/v1/auth/session_db_auth.py, 👉 api/v1/app.py, 👉 models/user_session.py
[:point_right: 👉 api/v1/auth/basic_auth.py
This project was done by SE. Moses Mwangi. Feel free to get intouch with me;
📱 WhatsApp +254115227963
📧 Email [email protected]
👍 A lot of thanks to ALX-Africa Software Engineering program for the project requirements.