Back-end - User Data
0x00. Personal data
Back-endAuthentification
By: Emmanuel Turlay, Staff Software Engineer at Cruise
Weight: 1
Project over - took place from Mar 1, 2023 5:00 AM to Mar 3, 2023 5:00 AM
Manual QA review was done by on Feb 12, 2023 10:15 AM
An auto review will be launched at the deadline
In a nutshell…
Manual QA review: 0.0/3 mandatory
Auto QA review: 32.0/32 mandatory
Altogether: 91.43%
Mandatory: 91.43%
Optional: no optional tasks
Back-end - User Data 1
Resources
Read or watch:
What Is PII, non-PII, and Personal Data?
logging documentation
bcrypt package
Logging to Files, Setting Levels, and Formatting
Learning Objectives
At the end of this project, you are expected to be able to explain to anyone, without the help
of Google:
Examples of Personally Identifiable Information (PII)
How to implement a log filter that will obfuscate PII fields
How to encrypt a password and check the validity of an input password
How to authenticate to a database using environment variables
Back-end - User Data 2
Requirements
All your files will be interpreted/compiled on Ubuntu 18.04 LTS using python3 (version 3.7)
All your files should end with a new line
The first line of all your files should be exactly #!/usr/bin/env python3
A README.md file, at the root of the folder of the project, is mandatory
Your code should use the pycodestyle style (version 2.5)
All your files must be executable
The length of your files will be tested using wc
All your modules should have a documentation ( python3 -c
'print(__import__("my_module").__doc__)' )
All your classes should have a documentation ( python3 -c
'print(__import__("my_module").MyClass.__doc__)' )
All your functions (inside and outside a class) should have a documentation ( python3 -c
'print(__import__("my_module").my_function.__doc__)' and python3 -c
'print(__import__("my_module").MyClass.my_function.__doc__)' )
A documentation is not a simple word, it’s a real sentence explaining what’s the purpose of
the module, class or method (the length of it will be verified)
All your functions should be type annotated
Tasks
0. Regex-ing
mandatory
Score: 100.0% (Checks completed: 100.0%)
Write a function called filter_datum that returns the log message obfuscated:
Arguments:
fields : a list of strings representing all fields to obfuscate
redaction : a string representing by what the field will be obfuscated
message : a string representing the log line
separator : a string representing by which character is separating all fields in the log
line ( message )
The function should use a regex to replace occurrences of certain field values.
Back-end - User Data 3
filter_datum should be less than 5 lines long and use re.sub to perform the substitution
with a single regex.
bob@dylan:~$ cat main.py
#!/usr/bin/env python3
"""
Main file
"""
filter_datum = __import__('filtered_logger').filter_datum
fields = ["password", "date_of_birth"]
messages = ["name=egg;email=eggmin@eggsample.com;password=eggcellent;date_of_birth=12/12/1986;",
"name=bob;email=bob@dylan.com;password=bobbycool;date_of_birth=03/04/1993;"]
for message in messages:
print(filter_datum(fields, 'xxx', message, ';'))
bob@dylan:~$
bob@dylan:~$ ./main.py
name=egg;email=eggmin@eggsample.com;password=xxx;date_of_birth=xxx;
name=bob;email=bob@dylan.com;password=xxx;date_of_birth=xxx;
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x00-personal_data
File: filtered_logger.py
Done! Help Check your code Get a sandbox QA Review
1. Log formatter
mandatory
Score: 100.0% (Checks completed: 100.0%)
Copy the following code into filtered_logger.py .
import logging
class RedactingFormatter(logging.Formatter):
""" Redacting Formatter class
"""
REDACTION = "***"
FORMAT = "[HOLBERTON] %(name)s %(levelname)s %(asctime)-15s: %(message)s"
SEPARATOR = ";"
def __init__(self):
super(RedactingFormatter, self).__init__(self.FORMAT)
def format(self, record: logging.LogRecord) -> str:
NotImplementedError
Back-end - User Data 4
Update the class to accept a list of strings fields constructor argument.
Implement the format method to filter values in incoming log records using filter_datum .
Values for fields in fields should be filtered.
DO NOT extrapolate FORMAT manually. The format method should be less than 5 lines long.
bob@dylan:~$ cat main.py
#!/usr/bin/env python3
"""
Main file
"""
import logging
import re
RedactingFormatter = __import__('filtered_logger').RedactingFormatter
message = "name=Bob;email=bob@dylan.com;ssn=000-123-0000;password=bobby2019;"
log_record = logging.LogRecord("my_logger", logging.INFO, None, None, message, None, None)
formatter = RedactingFormatter(fields=("email", "ssn", "password"))
print(formatter.format(log_record))
bob@dylan:~$
bob@dylan:~$ ./main.py
[HOLBERTON] my_logger INFO 2019-11-19 18:24:25,105: name=Bob; email=***; ssn=***; password=***;
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x00-personal_data
File: filtered_logger.py
Done! Help Check your code Get a sandbox QA Review
2. Create logger
mandatory
Score: 100.0% (Checks completed: 100.0%)
Use user_data.csv for this task
Implement a get_logger function that takes no arguments and returns a logging.Logger object.
The logger should be named "user_data" and only log up to logging.INFO level. It should not
propagate messages to other loggers. It should have
a StreamHandler with RedactingFormatter as formatter.
Create a tuple PII_FIELDS constant at the root of the module containing the fields
from user_data.csv that are considered PII. PII_FIELDS can contain only 5 fields - choose the
right list of fields that can are considered as “important” PIIs or information that you must
hide in your logs. Use it to parameterize the formatter.
Back-end - User Data 5
Tips:
What Is PII, non-PII, and personal data?
Uncovering Password Habits
bob@dylan:~$ cat main.py
#!/usr/bin/env python3
"""
Main file
"""
import logging
get_logger = __import__('filtered_logger').get_logger
PII_FIELDS = __import__('filtered_logger').PII_FIELDS
print(get_logger.__annotations__.get('return'))
print("PII_FIELDS: {}".format(len(PII_FIELDS)))
bob@dylan:~$
bob@dylan:~$ ./main.py
<class 'logging.Logger'>
PII_FIELDS: 5
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x00-personal_data
File: filtered_logger.py
Done! Help Check your code Get a sandbox QA Review
3. Connect to secure database
mandatory
Score: 100.0% (Checks completed: 100.0%)
Database credentials should NEVER be stored in code or checked into version control. One
secure option is to store them as environment variable on the application server.
In this task, you will connect to a secure holberton database to read a users table. The
database is protected by a username and password that are set as environment variables on
the server named PERSONAL_DATA_DB_USERNAME (set the default as
“root”), PERSONAL_DATA_DB_PASSWORD (set the default as an empty string)
and PERSONAL_DATA_DB_HOST (set the default as “localhost”).
The database name is stored in PERSONAL_DATA_DB_NAME .
Implement a get_db function that returns a connector to the database
( mysql.connector.connection.MySQLConnection object).
Use the os module to obtain credentials from the environment
Back-end - User Data 6
Use the module mysql-connector-python to connect to the MySQL database ( pip3 install
mysql-connector-python )
bob@dylan:~$ cat main.sql
-- setup mysql server
-- configure permissions
CREATE DATABASE IF NOT EXISTS my_db;
CREATE USER IF NOT EXISTS root@localhost IDENTIFIED BY 'root';
GRANT ALL PRIVILEGES ON my_db.* TO 'root'@'localhost';
USE my_db;
DROP TABLE IF EXISTS users;
CREATE TABLE users (
email VARCHAR(256)
);
INSERT INTO users(email) VALUES ("bob@dylan.com");
INSERT INTO users(email) VALUES ("bib@dylan.com");
bob@dylan:~$
bob@dylan:~$ cat main.sql | mysql -uroot -p
Enter password:
bob@dylan:~$
bob@dylan:~$ echo "SELECT COUNT(*) FROM users;" | mysql -uroot -p my_db
Enter password:
2
bob@dylan:~$
bob@dylan:~$ cat main.py
#!/usr/bin/env python3
"""
Main file
"""
get_db = __import__('filtered_logger').get_db
db = get_db()
cursor = db.cursor()
cursor.execute("SELECT COUNT(*) FROM users;")
for row in cursor:
print(row[0])
cursor.close()
db.close()
bob@dylan:~$
bob@dylan:~$ PERSONAL_DATA_DB_USERNAME=root PERSONAL_DATA_DB_PASSWORD=root PERSONAL_DATA_DB_HOST=
localhost PERSONAL_DATA_DB_NAME=my_db ./main.py
2
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x00-personal_data
File: filtered_logger.py
Done! Help Check your code Get a sandbox QA Review
Back-end - User Data 7
4. Read and filter data
mandatory
Score: 0.0% (Checks completed: 0.0%)
Implement a main function that takes no arguments and returns nothing.
The function will obtain a database connection using get_db and retrieve all rows in
the users table and display each row under a filtered format like this:
[HOLBERTON] user_data INFO 2019-11-19 18:37:59,596: name=***; email=***; phone=***; ssn=***; pass
word=***; ip=e848:e856:4e0b:a056:54ad:1e98:8110:ce1b; last_login=2019-11-14T06:16:24; user_agent=
Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; KTXN);
Filtered fields:
name
email
phone
ssn
password
Only your main function should run when the module is executed.
bob@dylan:~$ cat main.sql
-- setup mysql server
-- configure permissions
CREATE DATABASE IF NOT EXISTS my_db;
CREATE USER IF NOT EXISTS root@localhost IDENTIFIED BY 'root';
GRANT ALL PRIVILEGES ON my_db.* TO root@localhost;
USE my_db;
DROP TABLE IF EXISTS users;
CREATE TABLE users (
name VARCHAR(256),
email VARCHAR(256),
phone VARCHAR(16),
ssn VARCHAR(16),
password VARCHAR(256),
ip VARCHAR(64),
last_login TIMESTAMP,
user_agent VARCHAR(512)
);
INSERT INTO users(name, email, phone, ssn, password, ip, last_login, user_agent) VALUES ("Marlene
Wood","hwestiii@att.net","(473) 401-4253","261-72-6780","K5?BMNv","60ed:c396:2ff:244:bbd0:9208:26
f2:93ea","2019-11-14 06:14:24","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHT
ML, like Gecko) Chrome/74.0.3729.157 Safari/537.36");
INSERT INTO users(name, email, phone, ssn, password, ip, last_login, user_agent) VALUES ("Belen B
ailey","bcevc@yahoo.com","(539) 233-4942","203-38-5395","^3EZ~TkX","f724:c5d1:a14d:c4c5:bae2:945
7:3769:1969","2019-11-14 06:16:19","Mozilla/5.0 (Linux; U; Android 4.1.2; de-de; GT-I9100 Build/J
ZO54K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30");
Back-end - User Data 8
bob@dylan:~$
bob@dylan:~$ cat main.sql | mysql -uroot -p
Enter password:
bob@dylan:~$
bob@dylan:~$ echo "SELECT COUNT(*) FROM users;" | mysql -uroot -p my_db
Enter password:
2
bob@dylan:~$
bob@dylan:~$ PERSONAL_DATA_DB_USERNAME=root PERSONAL_DATA_DB_PASSWORD=root PERSONAL_DATA_DB_HOST=
localhost PERSONAL_DATA_DB_NAME=my_db ./filtered_logger.py
[HOLBERTON] user_data INFO 2019-11-19 18:37:59,596: name=***; email=***; phone=***; ssn=***; pass
word=***; ip=60ed:c396:2ff:244:bbd0:9208:26f2:93ea; last_login=2019-11-14 06:14:24; user_agent=Mo
zilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.1
57 Safari/537.36;
[HOLBERTON] user_data INFO 2019-11-19 18:37:59,621: name=***; email=***; phone=***; ssn=***; pass
word=***; ip=f724:c5d1:a14d:c4c5:bae2:9457:3769:1969; last_login=2019-11-14 06:16:19; user_agent=
Mozilla/5.0 (Linux; U; Android 4.1.2; de-de; GT-I9100 Build/JZO54K) AppleWebKit/534.30 (KHTML, li
ke Gecko) Version/4.0 Mobile Safari/534.30;
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x00-personal_data
File: filtered_logger.py
Done! Help Get a sandbox QA Review
5. Encrypting passwords
mandatory
Score: 100.0% (Checks completed: 100.0%)
User passwords should NEVER be stored in plain text in a database.
Implement a hash_password function that expects one string argument name password and
returns a salted, hashed password, which is a byte string.
Use the bcrypt package to perform the hashing (with hashpw ).
bob@dylan:~$ cat main.py
#!/usr/bin/env python3
"""
Main file
"""
hash_password = __import__('encrypt_password').hash_password
password = "MyAmazingPassw0rd"
print(hash_password(password))
print(hash_password(password))
bob@dylan:~$
bob@dylan:~$ ./main.py
b'$2b$12$Fnjf6ew.oPZtVksngJjh1.vYCnxRjPm2yt18kw6AuprMRpmhJVxJO'
b'$2b$12$xSAw.bxfSTAlIBglPMXeL.SJnzme3Gm0E7eOEKOVV2OhqOakyUN5m'
bob@dylan:~$
Back-end - User Data 9
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x00-personal_data
File: encrypt_password.py
Done! Help Check your code Get a sandbox QA Review
6. Check valid password
mandatory
Score: 100.0% (Checks completed: 100.0%)
Implement an is_valid function that expects 2 arguments and returns a boolean.
Arguments:
hashed_password : bytes type
password : string type
Use bcrypt to validate that the provided password matches the hashed password.
bob@dylan:~$ cat main.py
#!/usr/bin/env python3
"""
Main file
"""
hash_password = __import__('encrypt_password').hash_password
is_valid = __import__('encrypt_password').is_valid
password = "MyAmazingPassw0rd"
encrypted_password = hash_password(password)
print(encrypted_password)
print(is_valid(encrypted_password, password))
bob@dylan:~$
bob@dylan:~$ ./main.py
b'$2b$12$Fnjf6ew.oPZtVksngJjh1.vYCnxRjPm2yt18kw6AuprMRpmhJVxJO'
True
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x00-personal_data
File: encrypt_password.py
Back-end - User Data 10
0x01. Basic authentication
Back-endAuthentification
By: Guillaume, CTO at Holberton School
Weight: 1
Project over - took place from Mar 6, 2023 5:00 AM to Mar 8, 2023 5:00 AM
An auto review will be launched at the deadline
In a nutshell…
Auto QA review: 169.0/169 mandatory & 27.0/27 optional
Altogether: 200.0%
Mandatory: 100.0%
Optional: 100.0%
Calculation: 100.0% + (100.0% * 100.0%) == 200.0%
Background Context
In this project, you will learn what the authentication process means and implement a Basic
Authentication on a simple API.
In the industry, you should not implement your own Basic authentication system and use a
module or framework that doing it for you (like in Python-Flask: Flask-HTTPAuth). Here, for the
learning purpose, we will walk through each step of this mechanism to understand it by doing.
Back-end - User Data 11
Resources
Read or watch:
REST API Authentication Mechanisms
Base64 in Python
HTTP header Authorization
Flask
Base64 - concept
Learning Objectives
At the end of this project, you are expected to be able to explain to anyone, without the help
of Google:
General
What authentication means
What Base64 is
How to encode a string in Base64
What Basic authentication means
How to send the Authorization header
Requirements
Python Scripts
All your files will be interpreted/compiled on Ubuntu 18.04 LTS using python3 (version 3.7)
All your files should end with a new line
The first line of all your files should be exactly #!/usr/bin/env python3
A README.md file, at the root of the folder of the project, is mandatory
Your code should use the pycodestyle style (version 2.5)
All your files must be executable
The length of your files will be tested using wc
All your modules should have a documentation ( python3 -c
'print(__import__("my_module").__doc__)' )
Back-end - User Data 12
All your classes should have a documentation ( python3 -c
'print(__import__("my_module").MyClass.__doc__)' )
All your functions (inside and outside a class) should have a documentation ( python3 -c
'print(__import__("my_module").my_function.__doc__)' and python3 -c
'print(__import__("my_module").MyClass.my_function.__doc__)' )
A documentation is not a simple word, it’s a real sentence explaining what’s the purpose of
the module, class or method (the length of it will be verified)
Tasks
0. Simple-basic-API
mandatory
Score: 100.0% (Checks completed: 100.0%)
Download and start your project from this archive.zip
In this archive, you will find a simple API with one model: User . Storage of these users is done
via a serialization/deserialization in files.
Setup and start server
bob@dylan:~$ pip3 install -r requirements.txt
...
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 python3 -m api.v1.app
* Serving Flask app "app" (lazy loading)
...
bob@dylan:~$
Use the API (in another tab or in your browser)
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/status" -vvv
* Trying 0.0.0.0...
* TCP_NODELAY set
* Connected to 0.0.0.0 (127.0.0.1) port 5000 (#0)
> GET /api/v1/status HTTP/1.1
> Host: 0.0.0.0:5000
> User-Agent: curl/7.54.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: application/json
< Content-Length: 16
< Access-Control-Allow-Origin: *
< Server: Werkzeug/1.0.1 Python/3.7.5
< Date: Mon, 18 May 2020 20:29:21 GMT
<
{"status":"OK"}
Back-end - User Data 13
* Closing connection 0
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x01-Basic_authentication
Done! Help Check your code Get a sandbox QA Review
1. Error handler: Unauthorized
mandatory
Score: 100.0% (Checks completed: 100.0%)
What the HTTP status code for a request unauthorized? 401 of course!
Edit api/v1/app.py :
Add a new error handler for this status code, the response must be:
a JSON: {"error": "Unauthorized"}
status code 401
you must use jsonify from Flask
For testing this new error handler, add a new endpoint in api/v1/views/index.py :
Route: GET /api/v1/unauthorized
This endpoint must raise a 401 error by using abort - Custom Error Pages
By calling abort(401) , the error handler for 401 will be executed.
In the first terminal:
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 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/unauthorized"
{
"error": "Unauthorized"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/unauthorized" -vvv
* Trying 0.0.0.0...
* TCP_NODELAY set
* Connected to 0.0.0.0 (127.0.0.1) port 5000 (#0)
> GET /api/v1/unauthorized HTTP/1.1
> Host: 0.0.0.0:5000
> User-Agent: curl/7.54.0
Back-end - User Data 14
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 401 UNAUTHORIZED
< Content-Type: application/json
< Content-Length: 30
< Server: Werkzeug/0.12.1 Python/3.4.3
< Date: Sun, 24 Sep 2017 22:50:40 GMT
<
{
"error": "Unauthorized"
}
* Closing connection 0
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x01-Basic_authentication
File: api/v1/app.py, api/v1/views/index.py
Done! Help Check your code Get a sandbox QA Review
2. Error handler: Forbidden
mandatory
Score: 100.0% (Checks completed: 100.0%)
What the HTTP status code for a request where the user is authenticate but not allowed to
access to a resource? 403 of course!
Edit api/v1/app.py :
Add a new error handler for this status code, the response must be:
a JSON: {"error": "Forbidden"}
status code 403
you must use jsonify from Flask
For testing this new error handler, add a new endpoint in api/v1/views/index.py :
Route: GET /api/v1/forbidden
This endpoint must raise a 403 error by using abort - Custom Error Pages
By calling abort(403) , the error handler for 403 will be executed.
In the first terminal:
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 python3 -m api.v1.app
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
....
Back-end - User Data 15
In a second terminal:
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/forbidden"
{
"error": "Forbidden"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/forbidden" -vvv
* Trying 0.0.0.0...
* TCP_NODELAY set
* Connected to 0.0.0.0 (127.0.0.1) port 5000 (#0)
> GET /api/v1/forbidden HTTP/1.1
> Host: 0.0.0.0:5000
> User-Agent: curl/7.54.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 403 FORBIDDEN
< Content-Type: application/json
< Content-Length: 27
< Server: Werkzeug/0.12.1 Python/3.4.3
< Date: Sun, 24 Sep 2017 22:54:22 GMT
<
{
"error": "Forbidden"
}
* Closing connection 0
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x01-Basic_authentication
File: api/v1/app.py, api/v1/views/index.py
Done! Help Check your code Get a sandbox QA Review
3. Auth class
mandatory
Score: 100.0% (Checks completed: 100.0%)
Now you will create a class to manage the API authentication.
Create a folder api/v1/auth
Create an empty file api/v1/auth/__init__.py
Create the class Auth :
in the file api/v1/auth/auth.py
import request from flask
class name Auth
Back-end - User Data 16
public method def require_auth(self, path: str, excluded_paths: List[str]) -> bool: that
returns False - path and excluded_paths will be used later, now, you don’t need to take
care of them
public method def authorization_header(self, request=None) -> str: that
returns None - request will be the Flask request object
public method def current_user(self, request=None) -> TypeVar('User'): that
returns None - request will be the Flask request object
This class is the template for all authentication system you will implement.
bob@dylan:~$ cat main_0.py
#!/usr/bin/env python3
""" Main 0
"""
from api.v1.auth.auth import Auth
a = Auth()
print(a.require_auth("/api/v1/status/", ["/api/v1/status/"]))
print(a.authorization_header())
print(a.current_user())
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 ./main_0.py
False
None
None
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x01-Basic_authentication
File: api/v1/auth, api/v1/auth/__init__.py, api/v1/auth/auth.py
Done! Help Check your code Get a sandbox QA Review
4. Define which routes don't need authentication
mandatory
Score: 100.0% (Checks completed: 100.0%)
Update the method def require_auth(self, path: str, excluded_paths: List[str]) ->
bool: in Auth that returns True if the path is not in the list of strings excluded_paths :
Returns True if path is None
Returns True if excluded_paths is None or empty
Returns False if path is in excluded_paths
You can assume excluded_paths contains string path always ending by a /
Back-end - User Data 17
This method must be slash tolerant: path=/api/v1/status and path=/api/v1/status/ must be
returned False if excluded_paths contains /api/v1/status/
bob@dylan:~$ cat main_1.py
#!/usr/bin/env python3
""" Main 1
"""
from api.v1.auth.auth import Auth
a = Auth()
print(a.require_auth(None, None))
print(a.require_auth(None, []))
print(a.require_auth("/api/v1/status/", []))
print(a.require_auth("/api/v1/status/", ["/api/v1/status/"]))
print(a.require_auth("/api/v1/status", ["/api/v1/status/"]))
print(a.require_auth("/api/v1/users", ["/api/v1/status/"]))
print(a.require_auth("/api/v1/users", ["/api/v1/status/", "/api/v1/stats"]))
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 ./main_1.py
True
True
True
False
False
True
True
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x01-Basic_authentication
File: api/v1/auth/auth.py
Done! Help Check your code Get a sandbox QA Review
5. Request validation!
mandatory
Score: 100.0% (Checks completed: 100.0%)
Now you will validate all requests to secure the API:
Update the method def authorization_header(self, request=None) -> str: in api/v1/auth/auth.py :
If request is None , returns None
If request doesn’t contain the header key Authorization , returns None
Otherwise, return the value of the header request Authorization
Update the file api/v1/app.py :
Create a variable auth initialized to None after the CORS definition
Back-end - User Data 18
Based on the environment variable AUTH_TYPE , load and assign the right instance of
authentication to auth
if auth :
import Auth from api.v1.auth.auth
create an instance of Auth and assign it to the variable auth
Now the biggest piece is the filtering of each request. For that you will use the Flask
method before_request
Add a method in api/v1/app.py to handler before_request
if auth is None , do nothing
if is not part of this list ['/api/v1/status/', '/api/v1/unauthorized/',
request.path
'/api/v1/forbidden/'] , do nothing - you must use the method require_auth from
the auth instance
if auth.authorization_header(request) returns None , raise the error 401 - you must
use abort
if auth.current_user(request) returns None , raise the error 403 - you must use abort
In the first terminal:
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=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:~$
Repo:
Back-end - User Data 19
GitHub repository: alx-backend-user-data
Directory: 0x01-Basic_authentication
File: api/v1/app.py, api/v1/auth/auth.py
Done! Help Check your code Get a sandbox QA Review
6. Basic auth
mandatory
Score: 100.0% (Checks completed: 100.0%)
Create a class BasicAuth that inherits from Auth . For the moment this class will be empty.
Update api/v1/app.py for using BasicAuth class instead of Auth depending of the value of the
environment variable AUTH_TYPE , If AUTH_TYPE is equal to basic_auth :
import BasicAuth from api.v1.auth.basic_auth
create an instance of BasicAuth and assign it to the variable auth
Otherwise, keep the previous mechanism with auth an instance of Auth .
In the first terminal:
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/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:~$
Repo:
GitHub repository: alx-backend-user-data
Back-end - User Data 20
Directory: 0x01-Basic_authentication
File: api/v1/app.py, api/v1/auth/basic_auth.py
Done! Help Check your code Get a sandbox QA Review
7. Basic - Base64 part
mandatory
Score: 100.0% (Checks completed: 100.0%)
Add the method def extract_base64_authorization_header(self, authorization_header: str) ->
str: in the class BasicAuth that returns the Base64 part of the Authorization header for a Basic
Authentication:
Return None if authorization_header is None
Return None if authorization_header is not a string
Return None if authorization_header doesn’t start by Basic (with a space at the end)
Otherwise, return the value after Basic (after the space)
You can assume authorization_header contains only one Basic
bob@dylan:~$ cat main_2.py
#!/usr/bin/env python3
""" Main 2
"""
from api.v1.auth.basic_auth import BasicAuth
a = BasicAuth()
print(a.extract_base64_authorization_header(None))
print(a.extract_base64_authorization_header(89))
print(a.extract_base64_authorization_header("Holberton School"))
print(a.extract_base64_authorization_header("Basic Holberton"))
print(a.extract_base64_authorization_header("Basic SG9sYmVydG9u"))
print(a.extract_base64_authorization_header("Basic SG9sYmVydG9uIFNjaG9vbA=="))
print(a.extract_base64_authorization_header("Basic1234"))
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 ./main_2.py
None
None
None
Holberton
SG9sYmVydG9u
SG9sYmVydG9uIFNjaG9vbA==
None
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x01-Basic_authentication
Back-end - User Data 21
File: api/v1/auth/basic_auth.py
Done! Help Check your code Get a sandbox QA Review
8. Basic - Base64 decode
mandatory
Score: 100.0% (Checks completed: 100.0%)
Add the method def decode_base64_authorization_header(self, base64_authorization_header: str) ->
str: in the class BasicAuth that returns the decoded value of a Base64
string base64_authorization_header :
Return None if base64_authorization_header is None
Return None if base64_authorization_header is not a string
Return None if base64_authorization_header is not a valid Base64 - you can use try/except
Otherwise, return the decoded value as UTF8 string - you can use decode('utf-8')
bob@dylan:~$ cat main_3.py
#!/usr/bin/env python3
""" Main 3
"""
from api.v1.auth.basic_auth import BasicAuth
a = BasicAuth()
print(a.decode_base64_authorization_header(None))
print(a.decode_base64_authorization_header(89))
print(a.decode_base64_authorization_header("Holberton School"))
print(a.decode_base64_authorization_header("SG9sYmVydG9u"))
print(a.decode_base64_authorization_header("SG9sYmVydG9uIFNjaG9vbA=="))
print(a.decode_base64_authorization_header(a.extract_base64_authorization_header("Basic SG9sYmVyd
G9uIFNjaG9vbA==")))
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 ./main_3.py
None
None
None
Holberton
Holberton School
Holberton School
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x01-Basic_authentication
File: api/v1/auth/basic_auth.py
Done! Help Check your code Get a sandbox QA Review
Back-end - User Data 22
9. Basic - User credentials
mandatory
Score: 100.0% (Checks completed: 100.0%)
Add the method def extract_user_credentials(self, decoded_base64_authorization_header: str) ->
(str, str) in the class BasicAuth that returns the user email and password from the Base64
decoded value.
This method must return 2 values
Return None, None if decoded_base64_authorization_header is None
Return None, None if decoded_base64_authorization_header is not a string
Return None, None if decoded_base64_authorization_header doesn’t contain :
Otherwise, return the user email and the user password - these 2 values must be
separated by a :
You can assume decoded_base64_authorization_header will contain only one :
bob@dylan:~$ cat main_4.py
#!/usr/bin/env python3
""" Main 4
"""
from api.v1.auth.basic_auth import BasicAuth
a = BasicAuth()
print(a.extract_user_credentials(None))
print(a.extract_user_credentials(89))
print(a.extract_user_credentials("Holberton School"))
print(a.extract_user_credentials("Holberton:School"))
print(a.extract_user_credentials("bob@gmail.com:toto1234"))
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 ./main_4.py
(None, None)
(None, None)
(None, None)
('Holberton', 'School')
('bob@gmail.com', 'toto1234')
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x01-Basic_authentication
File: api/v1/auth/basic_auth.py
Done! Help Check your code Get a sandbox QA Review
10. Basic - User object
Back-end - User Data 23
mandatory
Score: 100.0% (Checks completed: 100.0%)
Add the method def user_object_from_credentials(self, user_email: str, user_pwd: str) ->
TypeVar('User'): in the class BasicAuth that returns the User instance based on his email and
password.
Return None if user_email is None or not a string
Return None if user_pwd is None or not a string
Return None if your database (file) doesn’t contain any User instance with email equal
to user_email - you should use the class method search of the User to lookup the list of
users based on their email. Don’t forget to test all cases: “what if there is no user in DB?”,
etc.
Return None if user_pwd is not the password of the User instance found - you must use the
method is_valid_password of User
Otherwise, return the User instance
bob@dylan:~$ cat main_5.py
#!/usr/bin/env python3
""" Main 5
"""
import uuid
from api.v1.auth.basic_auth import BasicAuth
from models.user import User
""" Create a user test """
user_email = str(uuid.uuid4())
user_clear_pwd = str(uuid.uuid4())
user = User()
user.email = user_email
user.first_name = "Bob"
user.last_name = "Dylan"
user.password = user_clear_pwd
print("New user: {}".format(user.display_name()))
user.save()
""" Retreive this user via the class BasicAuth """
a = BasicAuth()
u = a.user_object_from_credentials(None, None)
print(u.display_name() if u is not None else "None")
u = a.user_object_from_credentials(89, 98)
print(u.display_name() if u is not None else "None")
u = a.user_object_from_credentials("email@notfound.com", "pwd")
print(u.display_name() if u is not None else "None")
u = a.user_object_from_credentials(user_email, "pwd")
print(u.display_name() if u is not None else "None")
u = a.user_object_from_credentials(user_email, user_clear_pwd)
print(u.display_name() if u is not None else "None")
Back-end - User Data 24
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 ./main_5.py
New user: Bob Dylan
None
None
None
None
Bob Dylan
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x01-Basic_authentication
File: api/v1/auth/basic_auth.py
Done! Help Check your code Get a sandbox QA Review
11. Basic - Overload current_user - and BOOM!
mandatory
Score: 100.0% (Checks completed: 100.0%)
Now, you have all pieces for having a complete Basic authentication.
Add the method def current_user(self, request=None) -> TypeVar('User') in the
class BasicAuth that overloads Auth and retrieves the User instance for a request:
You must use authorization_header
You must use extract_base64_authorization_header
You must use decode_base64_authorization_header
You must use extract_user_credentials
You must use user_object_from_credentials
With this update, now your API is fully protected by a Basic Authentication. Enjoy!
In the first terminal:
bob@dylan:~$ cat main_6.py
#!/usr/bin/env python3
""" Main 6
"""
import base64
from api.v1.auth.basic_auth import BasicAuth
from models.user import User
""" Create a user test """
user_email = "bob@hbtn.io"
user_clear_pwd = "H0lbertonSchool98!"
user = User()
user.email = user_email
user.password = user_clear_pwd
Back-end - User Data 25
print("New user: {} / {}".format(user.id, user.display_name()))
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 ./main_6.py
New user: 9375973a-68c7-46aa-b135-29f79e837495 / bob@hbtn.io
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: Test"
{
"error": "Forbidden"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users" -H "Authorization: Basic test"
{
"error": "Forbidden"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users" -H "Authorization: Basic Ym9iQGhidG4uaW86SDB
sYmVydG9uU2Nob29sOTgh"
[
{
"created_at": "2017-09-25 01:55:17",
"email": "bob@hbtn.io",
"first_name": null,
"id": "9375973a-68c7-46aa-b135-29f79e837495",
"last_name": null,
"updated_at": "2017-09-25 01:55:17"
}
]
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x01-Basic_authentication
File: api/v1/auth/basic_auth.py
Done! Help Check your code Get a sandbox QA Review
Back-end - User Data 26
12. Basic - Allow password with ":"
#advanced
Score: 100.0% (Checks completed: 100.0%)
Improve the method def extract_user_credentials(self, decoded_base64_authorization_header) to
allow password with : .
In the first terminal:
bob@dylan:~$ cat main_100.py
#!/usr/bin/env python3
""" Main 100
"""
import base64
from api.v1.auth.basic_auth import BasicAuth
from models.user import User
""" Create a user test """
user_email = "bob100@hbtn.io"
user_clear_pwd = "H0lberton:School:98!"
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 ./main_100.py
New user: 5891469b-d2d5-4d33-b05d-02617d665368
Basic Base64: Ym9iMTAwQGhidG4uaW86SDBsYmVydG9uOlNjaG9vbDo5OCE=
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: Test"
{
"error": "Forbidden"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users" -H "Authorization: Basic test"
{
Back-end - User Data 27
"error": "Forbidden"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users" -H "Authorization: Basic Ym9iMTAwQGhidG4uaW8
6SDBsYmVydG9uOlNjaG9vbDo5OCE="
[
{
"created_at": "2017-09-25 01:55:17",
"email": "bob@hbtn.io",
"first_name": null,
"id": "9375973a-68c7-46aa-b135-29f79e837495",
"last_name": null,
"updated_at": "2017-09-25 01:55:17"
},
{
"created_at": "2017-09-25 01:59:42",
"email": "bob100@hbtn.io",
"first_name": null,
"id": "5891469b-d2d5-4d33-b05d-02617d665368",
"last_name": null,
"updated_at": "2017-09-25 01:59:42"
}
]
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x01-Basic_authentication
File: api/v1/auth/basic_auth.py
Done! Help Check your code Get a sandbox QA Review
13. Require auth with stars
#advanced
Score: 100.0% (Checks completed: 100.0%)
Improve def require_auth(self, path, excluded_paths) by allowing * at the end of excluded
paths.
Example for excluded_paths = ["/api/v1/stat*"] :
/api/v1/users will return True
/api/v1/status will return False
/api/v1/stats will return False
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x01-Basic_authentication
File: api/v1/auth/auth.py
Back-end - User Data 28
0x02. Session authentication
Back-endAuthentification
By: Guillaume, CTO at Holberton School
Weight: 1
Project over - took place from Mar 8, 2023 5:00 AM to Mar 10, 2023 5:00 AM
An auto review will be launched at the deadline
In a nutshell…
Auto QA review: 135.0/135 mandatory & 46.0/46 optional
Altogether: 200.0%
Mandatory: 100.0%
Optional: 100.0%
Calculation: 100.0% + (100.0% * 100.0%) == 200.0%
Background Context
In this project, you will implement a Session Authentication. You are not allowed to install any
other module.
In the industry, you should not implement your own Session authentication system and use a
module or framework that doing it for you (like in Python-Flask: Flask-HTTPAuth). Here, for the
learning purpose, we will walk through each step of this mechanism to understand it by doing.
Resources
Read or watch:
REST API Authentication Mechanisms - Only the session auth part
HTTP Cookie
Flask
Flask Cookie
Learning Objectives
At the end of this project, you are expected to be able to explain to anyone, without the help
of Google:
Back-end - User Data 29
General
What authentication means
What session authentication means
What Cookies are
How to send Cookies
How to parse Cookies
Requirements
Python Scripts
All your files will be interpreted/compiled on Ubuntu 18.04 LTS using python3 (version 3.7)
All your files should end with a new line
The first line of all your files should be exactly #!/usr/bin/env python3
A README.md file, at the root of the folder of the project, is mandatory
Your code should use the pycodestyle style (version 2.5)
All your files must be executable
The length of your files will be tested using wc
All your modules should have a documentation ( python3 -c
'print(__import__("my_module").__doc__)' )
All your classes should have a documentation ( python3 -c
'print(__import__("my_module").MyClass.__doc__)' )
All your functions (inside and outside a class) should have a documentation ( python3 -c
'print(__import__("my_module").my_function.__doc__)' and python3 -c
'print(__import__("my_module").MyClass.my_function.__doc__)' )
A documentation is not a simple word, it’s a real sentence explaining what’s the purpose of
the module, class or method (the length of it will be verified)
Tasks
0. Et moi et moi et moi!
mandatory
Score: 100.0% (Checks completed: 100.0%)
Copy all your work of the 0x06. Basic authentication project in this new folder.
Back-end - User Data 30
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 and api from the previous project 0x06. 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 in api/v1/app.py :
Assign the result of auth.current_user(request) to request.current_user
Update method for the route GET /api/v1/users/<user_id> in api/v1/views/users.py :
If <user_id> is equal to me and request.current_user is None : abort(404)
If <user_id> is equal to me and request.current_user is not None : return the
authenticated in a JSON response (like a normal case of
User GET
/api/v1/users/<user_id> where <user_id> is a valid User ID)
Otherwise, keep the same behavior
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 = "bob@hbtn.io"
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
Back-end - User Data 31
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 Ym9iQGhidG4uaW86SDB
sYmVydG9uU2Nob29sOTgh"
[
{
"created_at": "2017-09-25 01:55:17",
"email": "bob@hbtn.io",
"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 Ym9iQGhidG4uaW86
SDBsYmVydG9uU2Nob29sOTgh"
{
"created_at": "2017-09-25 01:55:17",
"email": "bob@hbtn.io",
"first_name": null,
"id": "9375973a-68c7-46aa-b135-29f79e837495",
"last_name": null,
"updated_at": "2017-09-25 01:55:17"
}
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x02-Session_authentication
File: api/v1/app.py, api/v1/views/users.py
Done! Help Check your code Get a sandbox QA Review
1. Empty session
mandatory
Score: 100.0% (Checks completed: 100.0%)
Back-end - User Data 32
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 authdepending of the
value of the environment variable AUTH_TYPE , If AUTH_TYPE is equal to session_auth :
import SessionAuth from api.v1.auth.session_auth
create an instance of SessionAuth and assign it to the variable auth
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:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x02-Session_authentication
File: api/v1/auth/session_auth.py, api/v1/app.py
Done! Help Check your code Get a sandbox QA Review
2. Create a session
Back-end - User Data 33
mandatory
Score: 100.0% (Checks completed: 100.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 a user_id :
Return None if user_id is None
Return None if user_id is not a string
Otherwise:
Generate a Session ID using uuid module and uuid4() like id in Base
Use this Session ID as key of the dictionary user_id_by_session_id - the value for
this key must be user_id
Return the Session ID
The same user_id can have multiple Session ID - indeed, the user_id is the value in
the dictionary user_id_by_session_id
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))
Back-end - User Data 34
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': 'fghi
j'}
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x02-Session_authentication
File: api/v1/auth/session_auth.py
Done! Help Check your code Get a sandbox QA Review
3. User ID for Session ID
mandatory
Score: 100.0% (Checks completed: 100.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 if session_id is None
Return None if session_id is not a string
Return the value (the User ID) for the key session_id in the
dictionary user_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"
Back-end - User Data 35
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': 'abcd
e'}
5d2930ba-f6d6-4a23-83d2-4f0abc8b8eee => abcde
8647f981-f503-4638-af23-7bb4a9e4b53f => abcde
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x02-Session_authentication
Back-end - User Data 36
File: api/v1/auth/session_auth.py
Done! Help Check your code Get a sandbox QA Review
4. Session cookie
mandatory
Score: 100.0% (Checks completed: 100.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 if request is None
Return the value of the cookie named _my_session_id from request - the name of the cookie
must be defined by the environment variable SESSION_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
Back-end - User Data 37
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:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x02-Session_authentication
File: api/v1/auth/auth.py
Done! Help Check your code Get a sandbox QA Review
5. Before request
mandatory
Score: 100.0% (Checks completed: 100.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
method require_auth - this route doesn’t exist yet but it should be accessible outside
authentication
If auth.authorization_header(request) and auth.session_cookie(request) return None , 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 py
thon3 -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" b
y 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 Ym9iQGhidG4uaW86
Back-end - User Data 38
SDBsYmVydG9uU2Nob29sOTgh" # Won't work because the environment variable AUTH_TYPE is equal to "se
ssion_auth"
{
"error": "Forbidden"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/users/me" --cookie "_my_session_id=5535d4d7-3d77-4d
06-8281-495dc3acfe76" # Won't work because no user is linked to this Session ID
{
"error": "Forbidden"
}
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x02-Session_authentication
File: api/v1/app.py
Done! Help Check your code Get a sandbox QA Review
6. Use Session ID for identifying a User
mandatory
Score: 100.0% (Checks completed: 100.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(...) and self.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 use User.get(...) for retrieving a User 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 = "bobsession@hbtn.io"
user_clear_pwd = "fake pwd"
user = User()
user.email = user_email
user.password = user_clear_pwd
Back-end - User Data 39
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-5f9d
7f9e9485
* 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-5f9d7f9
e9485"
User found: cf3ddee1-ff24-49e4-a40b-2540333fe992
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x02-Session_authentication
File: api/v1/auth/session_auth.py
Done! Help Check your code Get a sandbox QA Review
7. New view for Session Authentication
mandatory
Score: 100.0% (Checks completed: 100.0%)
Back-end - User Data 40
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 retrieve email and password parameters
If email is missing or empty, return the JSON { "error": "email missing" } with the status
code 400
If password is missing or empty, return the JSON { "error": "password missing" } with the
status code 400
Retrieve the User instance based on the email - you must use the class
method search of User (same as the one used for the BasicAuth )
If no User found, return the JSON { "error": "no user found for this email" } with the
status code 404
If the password is not the one of the User found, return the JSON { "error": "wrong
password" } with the status code 401 - you must use is_valid_password from
the User 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 use to_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
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 py
thon3 -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>
Back-end - User Data 41
<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=guillaume@hbt
n.io"
{
"error": "password missing"
}
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/auth_session/login" -XPOST -d "email=guillaume@hbt
n.io" -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=bobsession@hbt
n.io" -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=bobsession@hbt
n.io" -d "password=fake pwd"
{
"created_at": "2017-10-16 04:23:04",
"email": "bobsession@hbtn.io",
"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=bobsession@hbt
n.io" -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": "bobsession@hbtn.io",
"first_name": null,
"id": "cf3ddee1-ff24-49e4-a40b-2540333fe992",
Back-end - User Data 42
"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-44
4c-a0cc-ba0d167889c4"
{
"created_at": "2017-10-16 04:23:04",
"email": "bobsession@hbtn.io",
"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).
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x02-Session_authentication
File: api/v1/views/session_auth.py, api/v1/views/__init__.py
Done! Help Check your code Get a sandbox QA Review
8. Logout
mandatory
Score: 100.0% (Checks completed: 100.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 to None , return False
If the request doesn’t contain the Session ID cookie, return False - you must
use self.session_cookie(request)
If the Session ID of the request is not linked to any User ID, return False - you must
use self.user_id_for_session_id(...)
Otherwise, delete in self.user_id_by_session_id the Session ID (as key of this dictionary)
and return True
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
Back-end - User Data 43
You must use auth.destroy_session(request) for deleting the Session ID contains in the
request as cookie:
If destroy_session returns False , abort(404)
Otherwise, return an empty JSON dictionary with the status code 200
In the first terminal:
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=session_auth SESSION_NAME=_my_session_id py
thon3 -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=bobsession@hbt
n.io" -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": "bobsession@hbtn.io",
"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-4e
3a-9e6f-bcd345b24721"
{
"created_at": "2017-10-16 04:23:04",
"email": "bobsession@hbtn.io",
"first_name": null,
"id": "cf3ddee1-ff24-49e4-a40b-2540333fe992",
"last_name": null,
"updated_at": "2017-10-16 04:23:04"
}
Back-end - User Data 44
bob@dylan:~$
bob@dylan:~$ curl "http://0.0.0.0:5000/api/v1/auth_session/logout" --cookie "_my_session_id=e173c
b79-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=e173c
b79-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-4e
3a-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.
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x02-Session_authentication
File: api/v1/auth/session_auth.py, api/v1/views/session_auth.py
Done! Help Check your code Get a sandbox QA Review
9. Expiration?
#advanced
Score: 100.0% (Checks completed: 100.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
Back-end - User Data 45
Overload def create_session(self, user_id=None):
Create a Session ID by calling super() - super() will call the create_session() method
of SessionAuth
Return None if super() 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 variable user_id
The key created_at must be set to the current datetime - you must
use datetime.now()
Return the Session ID created
Overload def user_id_for_session_id(self, session_id=None):
Return None if session_id is None
Return None if user_id_by_session_id doesn’t contain any key equals to session_id
Return the user_id key from the session dictionary if self.session_duration is equal or
under 0
Return None if session dictionary doesn’t contain a key created_at
Return None if the created_at + session_duration seconds are before the current
datetime. datetime - timedelta
Otherwise, return user_id from the session dictionary
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_i
d 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=bobsession@hbt
n.io" -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
Back-end - User Data 46
> 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": "bobsession@hbtn.io",
"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-46
f0-9e43-fd05029ae63f"
{
"created_at": "2017-10-16 04:23:04",
"email": "bobsession@hbtn.io",
"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-46
f0-9e43-fd05029ae63f"
{
"created_at": "2017-10-16 04:23:04",
"email": "bobsession@hbtn.io",
"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-46
f0-9e43-fd05029ae63f"
{
"error": "Forbidden"
}
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x02-Session_authentication
File: api/v1/auth/session_exp_auth.py, api/v1/app.py
Back-end - User Data 47
Done! Help Check your code Get a sandbox QA Review
10. Sessions in database
#advanced
Score: 100.0% (Checks completed: 100.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 in User but for these 2
attributes:
user_id : string
session_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
of UserSession and returns the Session ID
Overload def user_id_for_session_id(self, session_id=None): that returns the User ID by
requesting UserSession in the database based on session_id
Overload def destroy_session(self, request=None): that destroys the UserSession 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=bobsession@hbt
n.io" -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
Back-end - User Data 48
> 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": "bobsession@hbtn.io",
"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-48
30-b1b2-3d77dfb9ad13"
{
"created_at": "2017-10-16 04:23:04",
"email": "bobsession@hbtn.io",
"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-48
30-b1b2-3d77dfb9ad13"
{
"created_at": "2017-10-16 04:23:04",
"email": "bobsession@hbtn.io",
"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-48
30-b1b2-3d77dfb9ad13"
{
"error": "Forbidden"
}
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x02-Session_authentication
Back-end - User Data 49
File: api/v1/auth/session_db_auth.py, api/v1/app.py, models/user_session.py
0x03. User authentication service
Back-endAuthentification
By: Emmanuel Turlay, Staff Software Engineer at Cruise
Weight: 1
Project over - took place from Mar 13, 2023 5:00 AM to Mar 17, 2023 5:00 AM
An auto review will be launched at the deadline
In a nutshell…
Auto QA review: 89.0/89 mandatory & 4.0/4 optional
Altogether: 200.0%
Mandatory: 100.0%
Optional: 100.0%
Calculation: 100.0% + (100.0% * 100.0%) == 200.0%
In the industry, you should not implement your own authentication system and use a module or
framework that doing it for you (like in Python-Flask: Flask-User). Here, for the learning
purpose, we will walk through each step of this mechanism to understand it by doing.
Back-end - User Data 50
Resources
Read or watch:
Flask documentation
Requests module
HTTP status codes
Learning Objectives
At the end of this project, you are expected to be able to explain to anyone, without the help
of Google:
How to declare API routes in a Flask app
How to get and set cookies
How to retrieve request form data
How to return various HTTP status codes
Requirements
Allowed editors: vi , vim , emacs
All your files will be interpreted/compiled on Ubuntu 18.04 LTS using python3 (version 3.7)
All your files should end with a new line
The first line of all your files should be exactly #!/usr/bin/env python3
A README.md file, at the root of the folder of the project, is mandatory
Your code should use the pycodestyle style (version 2.5)
You should use SQLAlchemy 1.3.x
All your files must be executable
The length of your files will be tested using wc
All your modules should have a documentation ( python3 -c
'print(__import__("my_module").__doc__)' )
All your classes should have a documentation ( python3 -c
'print(__import__("my_module").MyClass.__doc__)' )
All your functions (inside and outside a class) should have a documentation ( python3 -c
'print(__import__("my_module").my_function.__doc__)' and python3 -c
'print(__import__("my_module").MyClass.my_function.__doc__)' )
Back-end - User Data 51
A documentation is not a simple word, it’s a real sentence explaining what’s the purpose of
the module, class or method (the length of it will be verified)
All your functions should be type annotated
The flask app should only interact with Auth and never with DB directly.
Only public methods of Auth and DB should be used outside these classes
Setup
You will need to install bcrypt
pip3 install bcrypt
Tasks
0. User model
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task you will create a SQLAlchemy model named User for a database table
named users (by using the mapping declaration of SQLAlchemy).
The model will have the following attributes:
id , the integer primary key
email , a non-nullable string
hashed_password , a non-nullable string
session_id , a nullable string
reset_token , a nullable string
bob@dylan:~$ cat main.py
#!/usr/bin/env python3
"""
Main file
"""
from user import User
print(User.__tablename__)
for column in User.__table__.columns:
print("{}: {}".format(column, column.type))
bob@dylan:~$ python3 main.py
users
users.id: INTEGER
users.email: VARCHAR(250)
Back-end - User Data 52
users.hashed_password: VARCHAR(250)
users.session_id: VARCHAR(250)
users.reset_token: VARCHAR(250)
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: user.py
Done! Help Check your code Get a sandbox QA Review
1. create user
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will complete the DB class provided below to implement the add_user method.
"""DB module
"""
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.session import Session
from user import Base
class DB:
"""DB class
"""
def __init__(self) -> None:
"""Initialize a new DB instance
"""
self._engine = create_engine("sqlite:///a.db", echo=True)
Base.metadata.drop_all(self._engine)
Base.metadata.create_all(self._engine)
self.__session = None
@property
def _session(self) -> Session:
"""Memoized session object
"""
if self.__session is None:
DBSession = sessionmaker(bind=self._engine)
self.__session = DBSession()
return self.__session
Note that DB._session is a private property and hence should NEVER be used from outside
the DB class.
Back-end - User Data 53
Implement the add_user method, which has two required string
arguments: email and hashed_password , and returns a User object. The method should save the
user to the database. No validations are required at this stage.
bob@dylan:~$ cat main.py
#!/usr/bin/env python3
"""
Main file
"""
from db import DB
from user import User
my_db = DB()
user_1 = my_db.add_user("test@test.com", "SuperHashedPwd")
print(user_1.id)
user_2 = my_db.add_user("test1@test.com", "SuperHashedPwd1")
print(user_2.id)
bob@dylan:~$ python3 main.py
1
2
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: db.py
Done! Help Check your code Get a sandbox QA Review
2. Find user
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task you will implement the DB.find_user_by method. This method takes in arbitrary
keyword arguments and returns the first row found in the users table as filtered by the
method’s input arguments. No validation of input arguments required at this point.
Make sure that SQLAlchemy’s NoResultFound and InvalidRequestError are raised when no
results are found, or when wrong query arguments are passed, respectively.
Warning:
NoResultFound has been moved from sqlalchemy.orm.exc to sqlalchemy.exc between the
version 1.3.x and 1.4.x of SQLAchemy - please make sure you are importing it
from sqlalchemy.orm.exc
Back-end - User Data 54
bob@dylan:~$ cat main.py
#!/usr/bin/env python3
"""
Main file
"""
from db import DB
from user import User
from sqlalchemy.exc import InvalidRequestError
from sqlalchemy.orm.exc import NoResultFound
my_db = DB()
user = my_db.add_user("test@test.com", "PwdHashed")
print(user.id)
find_user = my_db.find_user_by(email="test@test.com")
print(find_user.id)
try:
find_user = my_db.find_user_by(email="test2@test.com")
print(find_user.id)
except NoResultFound:
print("Not found")
try:
find_user = my_db.find_user_by(no_email="test@test.com")
print(find_user.id)
except InvalidRequestError:
print("Invalid")
bob@dylan:~$ python3 main.py
1
1
Not found
Invalid
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: db.py
Done! Help Check your code Get a sandbox QA Review
3. update user
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement the DB.update_user method that takes as argument a
required user_id integer and arbitrary keyword arguments, and returns None .
The method will use find_user_by to locate the user to update, then will update the user’s
attributes as passed in the method’s arguments then commit changes to the database.
Back-end - User Data 55
If an argument that does not correspond to a user attribute is passed, raise a ValueError .
bob@dylan:~$ cat main.py
#!/usr/bin/env python3
"""
Main file
"""
from db import DB
from user import User
from sqlalchemy.exc import InvalidRequestError
from sqlalchemy.orm.exc import NoResultFound
my_db = DB()
email = 'test@test.com'
hashed_password = "hashedPwd"
user = my_db.add_user(email, hashed_password)
print(user.id)
try:
my_db.update_user(user.id, hashed_password='NewPwd')
print("Password updated")
except ValueError:
print("Error")
bob@dylan:~$ python3 main.py
1
Password updated
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: db.py
Done! Help Check your code Get a sandbox QA Review
4. Hash password
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task you will define a _hash_password method that takes in a password string arguments
and returns bytes.
The returned bytes is a salted hash of the input password, hashed with bcrypt.hashpw .
bob@dylan:~$ cat main.py
#!/usr/bin/env python3
"""
Main file
"""
Back-end - User Data 56
from auth import _hash_password
print(_hash_password("Hello Holberton"))
bob@dylan:~$ python3 main.py
b'$2b$12$eUDdeuBtrD41c8dXvzh95ehsWYCCAi4VH1JbESzgbgZT.eMMzi.G2'
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: auth.py
Done! Help Check your code Get a sandbox QA Review
5. Register user
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement the Auth.register_user in the Auth class provided below:
from db import DB
class Auth:
"""Auth class to interact with the authentication database.
"""
def __init__(self):
self._db = DB()
Note that Auth._db is a private property and should NEVER be used from outside the class.
Auth.register_user should take mandatory email and password string arguments and return
a User object.
If a user already exist with the passed email, raise a ValueError with the message User <user's
email> already exists .
If not, hash the password with _hash_password , save the user to the database
using self._db and return the User object.
bob@dylan:~$ cat main.py
#!/usr/bin/env python3
"""
Main file
"""
from auth import Auth
email = 'me@me.com'
password = 'mySecuredPwd'
Back-end - User Data 57
auth = Auth()
try:
user = auth.register_user(email, password)
print("successfully created a new user!")
except ValueError as err:
print("could not create a new user: {}".format(err))
try:
user = auth.register_user(email, password)
print("successfully created a new user!")
except ValueError as err:
print("could not create a new user: {}".format(err))
bob@dylan:~$ python3 main.py
successfully created a new user!
could not create a new user: User me@me.com already exists
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: auth.py
Done! Help Check your code Get a sandbox QA Review
6. Basic Flask app
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will set up a basic Flask app.
Create a Flask app that has a single GET route ( "/" ) and use flask.jsonify to return a JSON
payload of the form:
{"message": "Bienvenue"}
Add the following code at the end of the module:
if __name__ == "__main__":
app.run(host="0.0.0.0", port="5000")
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: app.py
Back-end - User Data 58
Done! Help Check your code Get a sandbox QA Review
7. Register user
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement the end-point to register a user. Define a users function that
implements the POST /users route.
Import the Auth object and instantiate it at the root of the module as such:
from auth import Auth
AUTH = Auth()
The end-point should expect two form data fields: "email" and "password" . If the user does not
exist, the end-point should register it and respond with the following JSON payload:
{"email": "<registered email>", "message": "user created"}
If the user is already registered, catch the exception and return a JSON payload of the form
{"message": "email already registered"}
and return a 400 status code
Remember that you should only use AUTH in this app. DB is a lower abstraction that is proxied
by Auth .
Terminal 1:
bob@dylan:~$ python3 app.py
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
Terminal 2:
bob@dylan:~$ curl -XPOST localhost:5000/users -d 'email=bob@me.com' -d 'password=mySuperPwd' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (#0)
Back-end - User Data 59
> POST /users HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Length: 40
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 40 out of 40 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: application/json
< Content-Length: 52
< Server: Werkzeug/1.0.1 Python/3.7.3
< Date: Wed, 19 Aug 2020 00:03:18 GMT
<
{"email":"bob@me.com","message":"user created"}
bob@dylan:~$
bob@dylan:~$ curl -XPOST localhost:5000/users -d 'email=bob@me.com' -d 'password=mySuperPwd' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (#0)
> POST /users HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Length: 40
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 40 out of 40 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 400 BAD REQUEST
< Content-Type: application/json
< Content-Length: 39
< Server: Werkzeug/1.0.1 Python/3.7.3
< Date: Wed, 19 Aug 2020 00:03:33 GMT
<
{"message":"email already registered"}
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: app.py
Done! Help Check your code Get a sandbox QA Review
8. Credentials validation
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement the Auth.valid_login method. It should
expect email and password required arguments and return a boolean.
Back-end - User Data 60
Try locating the user by email. If it exists, check the password with bcrypt.checkpw . If it matches
return True . In any other case, return False .
bob@dylan:~$ cat main.py
#!/usr/bin/env python3
"""
Main file
"""
from auth import Auth
email = 'bob@bob.com'
password = 'MyPwdOfBob'
auth = Auth()
auth.register_user(email, password)
print(auth.valid_login(email, password))
print(auth.valid_login(email, "WrongPwd"))
print(auth.valid_login("unknown@email", password))
bob@dylan:~$ python3 main.py
True
False
False
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: auth.py
Done! Help Check your code Get a sandbox QA Review
9. Generate UUIDs
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task you will implement a _generate_uuid function in the auth module. The function
should return a string representation of a new UUID. Use the uuid module.
Note that the method is private to the auth module and should NOT be used outside of it.
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: auth.py
Done! Help Check your code Get a sandbox QA Review
Back-end - User Data 61
10. Get session ID
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement the Auth.create_session method. It takes an email string
argument and returns the session ID as a string.
The method should find the user corresponding to the email, generate a new UUID and store it
in the database as the user’s session_id , then return the session ID.
Remember that only public methods of self._db can be used.
bob@dylan:~$ cat main.py
#!/usr/bin/env python3
"""
Main file
"""
from auth import Auth
email = 'bob@bob.com'
password = 'MyPwdOfBob'
auth = Auth()
auth.register_user(email, password)
print(auth.create_session(email))
print(auth.create_session("unknown@email.com"))
bob@dylan:~$ python3 main.py
5a006849-343e-4a48-ba4e-bbd523fcca58
None
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: auth.py
Done! Help Check your code Get a sandbox QA Review
11. Log in
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement a login function to respond to the POST /sessions route.
The request is expected to contain form data with "email" and a "password" fields.
If the login information is incorrect, use flask.abort to respond with a 401 HTTP status.
Otherwise, create a new session for the user, store it the session ID as a cookie with
key "session_id" on the response and return a JSON payload of the form
Back-end - User Data 62
{"email": "<user email>", "message": "logged in"}
bob@dylan:~$ curl -XPOST localhost:5000/users -d 'email=bob@bob.com' -d 'password=mySuperPwd'
{"email":"bob@bob.com","message":"user created"}
bob@dylan:~$
bob@dylan:~$ curl -XPOST localhost:5000/sessions -d 'email=bob@bob.com' -d 'password=mySuperPwd'
-v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (#0)
> POST /sessions HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Length: 37
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 37 out of 37 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: application/json
< Content-Length: 46
< Set-Cookie: session_id=163fe508-19a2-48ed-a7c8-d9c6e56fabd1; Path=/
< Server: Werkzeug/1.0.1 Python/3.7.3
< Date: Wed, 19 Aug 2020 00:12:34 GMT
<
{"email":"bob@bob.com","message":"logged in"}
* Closing connection 0
bob@dylan:~$
bob@dylan:~$ curl -XPOST localhost:5000/sessions -d 'email=bob@bob.com' -d 'password=BlaBla' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (#0)
> POST /sessions HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Length: 34
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 34 out of 34 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 401 UNAUTHORIZED
< Content-Type: text/html; charset=utf-8
< Content-Length: 338
< Server: Werkzeug/1.0.1 Python/3.7.3
< Date: Wed, 19 Aug 2020 00:12:45 GMT
<
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>401 Unauthorized</title>
<h1>Unauthorized</h1>
<p>The server could not verify that you are authorized to access the URL requested. You either su
pplied the wrong credentials (e.g. a bad password), or your browser doesn't understand how to sup
ply the credentials required.</p>
* Closing connection 0
bob@dylan:~$
Back-end - User Data 63
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: app.py
Done! Help Check your code Get a sandbox QA Review
12. Find user by session ID
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement the Auth.get_user_from_session_id method. It takes a
single session_id string argument and returns the corresponding User or None .
If the session ID is None or no user is found, return None . Otherwise return the corresponding
user.
Remember to only use public methods of self._db .
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: auth.py
Done! Help Check your code Get a sandbox QA Review
13. Destroy session
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement Auth.destroy_session . The method takes a single user_id integer
argument and returns None .
The method updates the corresponding user’s session ID to None .
Remember to only use public methods of self._db .
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: auth.py
Done! Help Check your code Get a sandbox QA Review
14. Log out
Back-end - User Data 64
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement a logout function to respond to the DELETE /sessions route.
The request is expected to contain the session ID as a cookie with key "session_id" .
Find the user with the requested session ID. If the user exists destroy the session and redirect
the user to GET / . If the user does not exist, respond with a 403 HTTP status.
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: app.py
Done! Help Check your code Get a sandbox QA Review
15. User profile
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement a profile function to respond to the GET /profile route.
The request is expected to contain a session_id cookie. Use it to find the user. If the user exist,
respond with a 200 HTTP status and the following JSON payload:
{"email": "<user email>"}
If the session ID is invalid or the user does not exist, respond with a 403 HTTP status.
bob@dylan:~$ curl -XPOST localhost:5000/sessions -d 'email=bob@bob.com' -d 'password=mySuperPwd'
-v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (#0)
> POST /sessions HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Length: 37
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 37 out of 37 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: application/json
< Content-Length: 46
< Set-Cookie: session_id=75c89af8-1729-44d9-a592-41b5e59de9a1; Path=/
< Server: Werkzeug/1.0.1 Python/3.7.3
< Date: Wed, 19 Aug 2020 00:15:57 GMT
<
Back-end - User Data 65
{"email":"bob@bob.com","message":"logged in"}
* Closing connection 0
bob@dylan:~$
bob@dylan:~$ curl -XGET localhost:5000/profile -b "session_id=75c89af8-1729-44d9-a592-41b5e59de9a
1"
{"email": "bob@bob.com"}
bob@dylan:~$
bob@dylan:~$ curl -XGET localhost:5000/profile -b "session_id=nope" -v
Note: Unnecessary use of -X or --request, GET is already inferred.
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (#0)
> GET /profile HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.58.0
> Accept: */*
> Cookie: session_id=75c89af8-1729-44d9-a592-41b5e59de9a
>
* HTTP 1.0, assume close after body
< HTTP/1.0 403 FORBIDDEN
< Content-Type: text/html; charset=utf-8
< Content-Length: 234
< Server: Werkzeug/1.0.1 Python/3.7.3
< Date: Wed, 19 Aug 2020 00:16:43 GMT
<
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>403 Forbidden</title>
<h1>Forbidden</h1>
<p>You don't have the permission to access the requested resource. It is either read-protected or
not readable by the server.</p>
* Closing connection 0
bob@dylan:~$
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: app.py
Done! Help Check your code Get a sandbox QA Review
16. Generate reset password token
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement the Auth.get_reset_password_token method. It take an email string
argument and returns a string.
Find the user corresponding to the email. If the user does not exist, raise
a ValueError exception. If it exists, generate a UUID and update the
user’s reset_token database field. Return the token.
Repo:
GitHub repository: alx-backend-user-data
Back-end - User Data 66
Directory: 0x03-user_authentication_service
File: auth.py
Done! Help Check your code Get a sandbox QA Review
17. Get reset password token
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement a get_reset_password_token function to respond to the POST
/reset_password route.
The request is expected to contain form data with the "email" field.
If the email is not registered, respond with a 403 status code. Otherwise, generate a token and
respond with a 200 HTTP status and the following JSON payload:
{"email": "<user email>", "reset_token": "<reset token>"}
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: app.py
Done! Help Check your code Get a sandbox QA Review
18. Update password
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement the Auth.update_password method. It takes reset_token string
argument and a password string argument and returns None .
Use the reset_token to find the corresponding user. If it does not exist, raise
a ValueError exception.
Otherwise, hash the password and update the user’s hashed_password field with the new hashed
password and the reset_token field to None .
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: auth.py
Done! Help Check your code Get a sandbox QA Review
Back-end - User Data 67
19. Update password end-point
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task you will implement the update_password function in the app module to respond to
the PUT /reset_password route.
The request is expected to contain form data with
fields "email" , "reset_token" and "new_password" .
Update the password. If the token is invalid, catch the exception and respond with a 403 HTTP
code.
If the token is valid, respond with a 200 HTTP code and the following JSON payload:
{"email": "<user email>", "message": "Password updated"}
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: app.py
Done! Help Check your code Get a sandbox QA Review
20. End-to-end integration test
#advanced
Score: 100.0% (Checks completed: 100.0%)
Start your app. Open a new terminal window.
Create a new module called main.py . Create one function for each of the following tasks. Use
the requests module to query your web server for the corresponding end-point. Use assert to
validate the response’s expected status code and payload (if any) for each task.
register_user(email: str, password: str) -> None
log_in_wrong_password(email: str, password: str) -> None
log_in(email: str, password: str) -> str
profile_unlogged() -> None
profile_logged(session_id: str) -> None
log_out(session_id: str) -> None
reset_password_token(email: str) -> str
update_password(email: str, reset_token: str, new_password: str) -> None
Back-end - User Data 68
Then copy the following code at the end of the main module:
EMAIL = "guillaume@holberton.io"
PASSWD = "b4l0u"
NEW_PASSWD = "t4rt1fl3tt3"
if __name__ == "__main__":
register_user(EMAIL, PASSWD)
log_in_wrong_password(EMAIL, NEW_PASSWD)
profile_unlogged()
session_id = log_in(EMAIL, PASSWD)
profile_logged(session_id)
log_out(session_id)
reset_token = reset_password_token(EMAIL)
update_password(EMAIL, reset_token, NEW_PASSWD)
log_in(EMAIL, NEW_PASSWD)
Run python main.py . If everything is correct, you should see no output.
Repo:
GitHub repository: alx-backend-user-data
Directory: 0x03-user_authentication_service
File: main.py
Back-end - User Data 69