Row Level Security
Row Level Security (RLS)
Blogify utilizes Supabase Row Level Security (RLS) to ensure that user data is protected at the database level. While the frontend provides a smooth user interface, RLS acts as a final, server-side gatekeeper that validates every request based on the user's identity.
Overview of Security Policies
Access to data in Blogify is governed by specific policies that distinguish between public readers and authenticated authors.
| Resource | Action | Access Level | Requirement |
| :--- | :--- | :--- | :--- |
| Posts | Read | Public | None |
| Posts | Create | Authenticated | Valid user session |
| Posts | Update/Delete | Owner Only | auth.uid() == user_id |
| Profiles | Read | Public | None |
| Profiles | Update | Owner Only | auth.uid() == id |
| Storage | Upload | Authenticated | Valid user session |
Data Ownership & Integrity
The core of Blogify’s security is the "Owner Only" policy. This ensures that even if a user attempts to bypass the UI and call the API directly, they cannot modify content they did not create.
Post Protection
Every post in the database is linked to a user_id. The RLS policy compares the user_id of the record with the uid() provided by the Supabase Auth token. If they do not match, the database rejects the update or deletion attempt.
Image Storage
Images uploaded via the dashboard are stored in a protected bucket. RLS policies ensure that while images are publicly viewable, only authenticated users can perform uploads to prevent unauthorized storage consumption.
Implementation in the API
When performing write operations, the lib/api.ts layer ensures a session is active before sending requests to the database. RLS then verifies this session server-side.
export async function updatePost(id: string, data: PostUpdate): Promise<Post> {
// 1. Client-side check for active session
const { data: sessionData } = await supabase.auth.getSession();
if (!sessionData.session) throw new Error('You must be logged in to update a post');
// 2. Database call (RLS validates that auth.uid() matches post.user_id)
const { data: row, error } = await supabase
.from('posts')
.update({ ...data, updated_at: new Date().toISOString() })
.eq('id', id)
.select()
.single();
if (error) throw new Error(error.message);
return mapPost(row);
}
UI Enforcement
To provide a better user experience, the Blogify UI hides or disables administrative actions (like Edit or Delete buttons) if the logged-in user's ID does not match the post's author ID.
// Example of conditional UI based on ownership
<div className="flex gap-2">
<EditPostButton postId={post.id} authorId={post.user_id} />
<DeletePostButton postId={post.id} authorId={post.user_id} />
</div>
If a user manages to navigate to an edit page for a post they do not own, the application includes a fallback check to redirect them back to the feed:
useEffect(() => {
getPost(id).then((post) => {
// Ownership guardrail
if (user && user.id !== post.user_id) {
router.push('/feed');
return;
}
// ... load post data
});
}, [id, user, router]);
Key Security Benefits
- Preventing Impersonation: Users cannot create posts on behalf of other users.
- Data Permanence: Only the original author has the authority to remove their content.
- Safe Public Browsing: Readers can explore all content without needing an account, while the underlying write-access remains strictly guarded.