0

Authentication vs authorization — who are you vs what are you allowed to do

Beginner5 min read·eng-19-001
interviewsecurity

Concept

Authentication vs Authorization — two distinct security concepts that are often confused.

Authentication (AuthN): Verifying IDENTITY. "Who are you?" The process of confirming that someone is who they claim to be. Examples: password login, OAuth, JWT verification, API key validation.

Authorization (AuthZ): Verifying PERMISSION. "What are you allowed to do?" After we know who you are, we decide what you can access. Examples: "only admins can delete users", "users can only edit their own posts", "this API key can only read, not write."

The sequence: Authentication MUST happen before authorization. You can't know if someone has permission until you know who they are.

Common mistakes:

  • Returning 401 when you mean 403 (and vice versa).
  • 401 Unauthorized: You are NOT authenticated. (Ironically named — it really means "unauthenticated.")
  • 403 Forbidden: You ARE authenticated but NOT authorized for this action.
  • Checking authentication but skipping authorization ("I'm logged in, so I can see any user's data").

In Laravel:

  • Authentication: auth(), Auth::check(), actingAs(), Sanctum/Jetstream/Breeze.
  • Authorization: Gates (Gate::allows()), Policies ($this->authorize()), can() middleware.

IDOR (Insecure Direct Object Reference): A common authorization failure. Authenticated user hits /api/orders/999 — if you only check authentication (is logged in?) but not authorization (does this user OWN order 999?), any user can read any order.

Code Example

php
<?php
// AUTHENTICATION — who are you?

// Check if user is authenticated
if (auth()->check()) {
    $user = auth()->user(); // authenticated User model
}

// Route middleware — require authentication
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/profile', [ProfileController::class, 'show']);
});

// AUTHORIZATION — what can you do?

// Gate — define permission globally
Gate::define('edit-post', function (User $user, Post $post) {
    return $user->id === $post->user_id; // user can only edit own posts
});

// Check authorization
if (Gate::allows('edit-post', $post)) {
    // can edit
}
if (Gate::denies('edit-post', $post)) {
    abort(403); // 403 Forbidden — authenticated but not allowed
}

// Policy — per-model authorization class
class PostPolicy
{
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }

    public function delete(User $user, Post $post): bool
    {
        return $user->id === $post->user_id || $user->isAdmin();
    }
}

// In controller
class PostController extends Controller
{
    public function update(Request $request, Post $post): Response
    {
        $this->authorize('update', $post); // 403 if not authorized
        $post->update($request->validated());
        return response()->noContent();
    }

    public function show(Post $post): Response
    {
        // IDOR FIX: always check ownership, not just authentication
        if ($post->user_id !== auth()->id()) {
            abort(403); // 403 not 404 (don't confirm existence of other users' posts)
        }
        return response()->json($post);
    }
}