nighthawkcoders / flask_portfolio

Python / Flask Starter Project
2 stars 169 forks source link

JWT Implementation #42

Open aidanywu opened 11 months ago

aidanywu commented 11 months ago

Most changes are based on this article

PyJWT requirement

We will be using PyJWT to encode and decode JWT tokens, so add that to requirements.txt

add secret key as app config

Reconfigure app.config['SECRET_KEY'] so that it tries to get the secret key (to encode and decode JWT) from the environment variable SECRET_KEY or it just uses "SECRET_KEY" as the secret key.

migration script hashes passwords and new valid hash method

To make sure user passwords aren't in plaintext for security reasons, we need to hash it when we initialize the default users inside the database in the migration script. The user set_password method was also changed to the same hash method and salt length as the initialization so new user accounts contain hashes from same hash method (previous method of only sha256 was giving an error as well).

middleware for checking JWT token & authentication

This is the authentication middleware that all requests to the web server will go through before accessing the API. It tries to get the JWT cookie from the request. If the cookie doesn't exist, a 401 Unauthorized Error is returned telling the user that the token is missing. Otherwise, it tries to decode the token if possible. Then it gets the uid from the token and sees if it is a existing user.

/authenticate returns generated JWT token

/api/users/authenticate now returns a JWT token from the request data json containing the uid and password. It does some checks on if the body exists, if the uid exists, if the user with the uid exists, and if the password is correct. If all of that passes, the JWT token containing the uid is encoded and returned as a cookie. @token_required is then added before the get and post methods for user CRUD to make requests going there go through the middleware first so only authenticated users can access those resources.

Note: Another easy way to implement JWT is using Flask-JWT-Extended

jm1021 commented 11 months ago

Summary of Changes and Purpose:

CORS Configuration:

Change: Added CORS support for both deployed and local runs. Purpose: CORS (Cross-Origin Resource Sharing) is configured to allow or restrict requests from different origins. This change ensures that your Flask application can handle requests from various sources.

cors = CORS(app, supports_credentials=True)

Dynamic Allowed Origins:

Change: Added a before_request hook to dynamically set the allowed origin based on the request's 'Origin' header. Purpose: Dynamically adjusting the allowed origins based on the incoming request helps ensure that only specified origins are allowed, preventing unauthorized cross-origin requests.

@app.before_request
def before_request():
    allowed_origin = request.headers.get('Origin')
    if allowed_origin in ['http://localhost:4100', 'http://127.0.0.1:4100', 'https://nighthawkcoders.github.io']:
        cors._origins = allowed_origin

SameSite Attribute in set_cookie:

Change: Added samesite='None' to the set_cookie method to allow cross-site requests. Purpose: Setting the SameSite attribute to 'None' is crucial for cookies to be sent with cross-site requests. This is required when working with front-end applications hosted on different domains.

resp.set_cookie(
    "jwt",
    token,
    max_age=3600,
    secure=True,
    httponly=True,
    path='/',
    samesite='None'
)

Nginx Preflighted Requests Configuration:

Change: Added Nginx configuration for handling preflighted requests, allowing only the specified frontend server. Purpose: Preflighted requests are pre-checks made by the browser before the actual request. This Nginx configuration ensures that only the designated frontend server is allowed for preflighted requests.

if ($request_method = OPTIONS) {
    add_header "Access-Control-Allow-Credentials" "true" always;
    add_header "Access-Control-Allow-Origin"  "https://nighthawkcoders.github.io" always;
    add_header "Access-Control-Allow-Methods" "GET, POST, PUT, OPTIONS, HEAD" always;
    add_header "Access-Control-Allow-MaxAge" 600 always;
    add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type, Accept" always;
    return 204;
}

These changes collectively enhance the security and compatibility of your Flask application with different frontend environments, ensuring proper handling of CORS and preflighted requests.