NiccoloB
NiccoloB

Reputation: 1

Flask session not persisted

I'm developing a web-application which uses Flask server-side and Angular client-side. I need to allow multiuser login in such application and I wanted to use flask session objects to handle login sessions, however sessions are not persisted between requests and everytime they are destroyed.

I've already followed some workaround (by adding specific headers to the request as well as to the response, flask_cors is configured and working).

Any suggestion?

Thanks :)

Edit:

this is my CORS initialization

CORS(app, supports_credentials=True, resources=r'/*')

and my login method that I use to populate the session object is defined as follows:

@app.route('/login', methods=['POST', 'GET'])
def login():
    print(request.headers)
    _json = request.json
    name = _json['username']
    password = _json['password']
    if name and password:
        m = hashlib.md5(password.encode('utf-8')).hexdigest()
        results = User.query.filter_by(username=name).first()
        if m == results.password:
            resp = jsonify("User logged in")
            resp.status_code = 200
            addRespHeaders(resp)
            session.permanent = True
            session['username'] = results.username
            session['role'] = results.role
            session.modified = True
            print(session['username'], session['role'])
            print(resp.headers)
            return resp
        else:
            resp = jsonify("Attention! Wrong Password")
            resp.status_code = 404
            addRespHeaders(resp)
            return resp
    else:
        resp = jsonify("Please enter the required fields")
        resp.status_code = 404
        addRespHeaders(resp)
        return resp

finally the addRespHeaders(resp) method:

def addRespHeaders(resp):
    resp.headers.add('Access-Control-Allow-Headers', "Origin, Content-Type, X-Requested-With, Accept, x-auth")
    resp.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')

When I call the login method and try to print the session['username'] or session['role'] it prints out the right values, but if in another method like:

@app.route('/user/getLoggedIn', methods=['GET'])
def getLoggedInUser():
    print('username' in session)
    logged_data = UserPersistence.query.all()[0].username
    logged_user = User.query.filter_by(username=logged_data).first()
    schema = UserSchema()
    resp = jsonify(schema.dump(logged_user))
    resp.status_code = 200
    addRespHeaders(resp)
    return resp

the print('username' in session) returns False.

Edit2:

This is how my service performing requests looks like:

    const httpOptions = {
    withCredentials: true,
    headers: new HttpHeaders({'Content-Type': 'application/json'})
};
@Injectable({providedIn: 'root'})
export class UserService{
    private userUrl = "http://192.168.0.88:5000";

    constructor(private http: HttpClient){ }

    /** GET users from server */
    getUsers(): Observable<User[]>{
        return this.http.get<User[]>(this.userUrl + '/users');
    }

    /** GET user by id. Will 404 if not found */
    getUser(id: number): Observable<any>{
        const url = `${this.userUrl}/user/${id}`;
        return this.http.get<User>(url);
    }

    getLoggedInUser(): Observable<User>{
        const url= `${this.userUrl}/user/getLoggedIn`;
        return this.http.get<User>(url);
    }
    /** POST: login user */
    login(username: string, password: string) {
        return this.http.post(this.userUrl + '/login', JSON.stringify({"username": username, "password": password}), httpOptions);
    }

    /** POST: logout logged in user */
    logout() {
        return this.http.post(this.userUrl + '/logout', httpOptions);
    }

    /** POST: add a new user to the server */
    addUser(user: User) {
        return this.http.post(this.userUrl + '/user/add', user, httpOptions);
    }

    /** PUT: update the user on the server */
    updateUser(user: User): Observable<any> {
        return this.http.put(this.userUrl + '/user/update', user, httpOptions);
    }

    /** PUT: update user password */
    updatePassword(user: PasswordReset): Observable<any> {
        return this.http.put(this.userUrl + '/user/changePassword', user, httpOptions);
    }

    /** DELETE: delete the user from the server */
    deleteUser(user: User | number) {
            const id = typeof user == 'number' ? user : user.id;
            const url = `${this.userUrl}/user/delete/${id}`;
            return this.http.delete(url, httpOptions);
    }
}

As far as I know the withCredentials: true should include credentials.

Edit3:

I managed to persist session data between requests while I'm in localhost in this way:

session_cookie = SecureCookieSessionInterface().get_signing_serializer(app)

@app.after_request
def after_request(response):
    origin = request.headers.get('Origin')
    if request.method == 'OPTIONS':
        response.headers.add('Access-Control-Allow-Credentials', 'true')
        response.headers.add('Access-Control-Allow-Headers', 'Content-Type')
        response.headers.add('Access-Control-Allow-Headers', 'x-csrf-token')
        response.headers.add('Access-Control-Allow-Methods',
                             'GET, POST, OPTIONS, PUT, PATCH, DELETE')
        if origin:
            response.headers.add('Access-Control-Allow-Origin', origin)
    else:
        response.headers.add('Access-Control-Allow-Credentials', 'true')
        if origin:
            response.headers.add('Access-Control-Allow-Origin', origin)
    same_cookie = session_cookie.dumps(dict(session))
    response.headers.add("Set-Cookie", f"{same_cookie}; Secure; HttpOnly; SameSite=None; Path=/;")
    return response

The problem now is that if I try to login in another PC under the same network, all requests are blocked since cookies are not passed between requests. Any idea? Thanks

Upvotes: 0

Views: 1963

Answers (1)

elBrambore
elBrambore

Reputation: 130

Did you added support_credentials=True option when initializing extension?

Here is more from docs:

Using CORS with cookies By default, Flask-CORS does not allow cookies to be submitted across sites, since it has potential security implications. If you wish to enable cross-site cookies, you may wish to add some sort of CSRF protection to keep you and your users safe.

To allow cookies or authenticated requests to be made cross origins, simply set the supports_credentials option to True. E.g.

from flask import Flask, session from flask_cors import CORS
    
app = Flask(__name__) 
CORS(app, supports_credentials=True)
    
@app.route("/")
    def helloWorld():   
        return "Hello, %s" % session['username']

Check also if your routes are whitelisted for CORS use or decorators are added to routes.

One of the simplest configurations. Exposes all resources matching /api/* to
CORS and allows the Content-Type header, which is necessary to POST JSON
cross origin.
CORS(app, resources=r'/api/*')


@app.route("/")
def helloWorld():
    """
        Since the path '/' does not match the regular expression r'/api/*',
        this route does not have CORS headers set.
    """
    return '''
<html>
    <h1>Hello CORS!</h1>
    <h3> End to end editable example with jquery! </h3>
    <a class="jsbin-embed" href="http://jsbin.com/zazitas/embed?js,console">JS Bin on jsbin.com</a>
    <script src="//static.jsbin.com/js/embed.min.js?3.35.12"></script>

</html>
'''

@app.route("/api/v1/users/")
def list_users():
    """
        Since the path matches the regular expression r'/api/*', this resource
        automatically has CORS headers set. The expected result is as follows:

        $ curl --include -X GET http://127.0.0.1:5000/api/v1/users/ \
            --header Origin:www.examplesite.com
        HTTP/1.0 200 OK
        Access-Control-Allow-Headers: Content-Type
        Access-Control-Allow-Origin: *
        Content-Length: 21
        Content-Type: application/json
        Date: Sat, 09 Aug 2014 00:26:41 GMT
        Server: Werkzeug/0.9.4 Python/2.7.8

        {
            "success": true
        }

    """
    return jsonify(user="joe")

Also, if you are using blueprints, remember to register blueprint to CORS.

Flask-CORS supports blueprints out of the box. Simply pass a blueprint instance to the CORS extension, and everything will just work.

api_v1 = Blueprint('API_v1', __name__)

CORS(api_v1) # enable CORS on the API_v1 blue print

All examples came from Flask-Cors docs. Link to docs

Upvotes: 1

Related Questions