Access control ensures users can only access resources they’re authorized to view or modify. This guide demonstrates authorization best practices based on the secure implementation.
Always verify user authentication before allowing access to protected resources:
secure/app.py
@app.route('/dashboard')def dashboard(): # Check if user is authenticated if 'user_id' not in session: flash('Debes iniciar sesión', 'warning') return redirect('/login') return render_template('dashboard.html', username=session.get('username'), role=session.get('role'))
1
Check session
Verify the user has an active session:
if 'user_id' not in session: return redirect('/login')
@app.route('/profile')def profile(): # Check authentication if 'user_id' not in session: flash('Debes iniciar sesión', 'warning') return redirect('/login') # Get requested user ID requested_id = request.args.get('id', session['user_id']) # Validate type try: requested_id = int(requested_id) except ValueError: flash('ID inválido', 'danger') return redirect('/dashboard') # Authorization check: Only allow viewing own profile # Unless user is an admin if requested_id != session['user_id'] and session.get('role') != 'admin': flash('No tienes permiso para ver este perfil', 'danger') return redirect('/dashboard') # User is authorized, fetch profile connection = create_connection() cursor = connection.cursor() query = "SELECT id, username, email, role, created_at FROM users WHERE id = %s" cursor.execute(query, (requested_id,)) user = cursor.fetchone() return render_template('profile.html', user=user)
This implementation prevents users from viewing other users’ profiles by modifying the ?id= parameter, unless they have admin role.
cursor.execute(""" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password TEXT NOT NULL, email TEXT, role TEXT DEFAULT 'user', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )""")
secure/app.py
# Store role in session during loginif user and check_password_hash(user['password'], password): session['user_id'] = user['id'] session['username'] = user['username'] session['role'] = user['role'] # Store role
@app.route('/profile')def profile(): # Authentication check if 'user_id' not in session: return redirect('/login') requested_id = request.args.get('id', session['user_id']) # Authorization check based on role if requested_id != session['user_id'] and session.get('role') != 'admin': flash('No tienes permiso para ver este perfil', 'danger') return redirect('/dashboard')
@app.route('/edit-post/<int:post_id>')def edit_post(post_id): if 'user_id' not in session: return redirect('/login') # Fetch post with ownership check query = "SELECT * FROM posts WHERE id = %s AND user_id = %s" cursor.execute(query, (post_id, session['user_id'])) post = cursor.fetchone() if not post: flash('Post not found or unauthorized', 'danger') return redirect('/dashboard') return render_template('edit_post.html', post=post)
@app.route('/delete-post/<int:post_id>')def delete_post(post_id): if 'user_id' not in session: return redirect('/login') cursor.execute("SELECT * FROM posts WHERE id = %s", (post_id,)) post = cursor.fetchone() # Allow if owner or admin if post['user_id'] != session['user_id'] and session.get('role') != 'admin': return abort(403) cursor.execute("DELETE FROM posts WHERE id = %s", (post_id,)) return redirect('/posts')
Enforce authorization at the database level when possible:
-- Always include user_id in WHERE clause for user resourcesSELECT * FROM posts WHERE id = ? AND user_id = ?-- Use database constraintsCREATE TABLE posts ( id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL, content TEXT, FOREIGN KEY (user_id) REFERENCES users(id));
Adding user_id to WHERE clauses ensures users can only access their own data, even if application-level checks fail.