You are on page 1of 69

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

You might also like