> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# UnauthorizedException

> 401 exception for authentication and authorization handling

`UnauthorizedException` is used when authentication is required or permissions are insufficient. It returns HTTP 401 status code and is used for situations like login required, session expired, or insufficient permissions.

## Basic Usage

```typescript theme={null}
class UnauthorizedException extends SoException {
  constructor(message: LocalizedString, payload?: unknown);
}
```

**Simple Example:**

```typescript theme={null}
@api()
async getMyProfile(ctx: Context) {
  if (!ctx.user) {
    throw new UnauthorizedException("Login required");
  }

  return this.findById(ctx.user.id);
}
```

## Practical Examples

### Basic Authentication Check

```typescript theme={null}
@api()
async updateMyProfile(
  ctx: Context,
  name: string,
  bio: string
) {
  if (!ctx.user) {
    throw new UnauthorizedException("Login required");
  }

  return this.update(ctx.user.id, { name, bio });
}
```

### Using Guards for Authentication (Recommended)

For simple login checks, using Guards is cleaner:

```typescript theme={null}
// Using Guards (recommended)
@api({ guards: ["user"] })
async getMyData(ctx: Context) {
  // ctx.user is guaranteed
  return this.findById(ctx.user!.id);
}

// Guard handling in sonamu.config.ts
export default {
  server: {
    apiConfig: {
      guardHandler: (guard, request, api) => {
        if (guard === "user" && !request.user) {
          throw new UnauthorizedException("Login required");
        }
        return true;
      }
    }
  }
} satisfies SonamuConfig;
```

### Resource Ownership Validation

```typescript theme={null}
@api()
async deletePost(ctx: Context, postId: number) {
  if (!ctx.user) {
    throw new UnauthorizedException("Login required");
  }

  const post = await this.findById(postId);

  if (!post) {
    throw new NotFoundException("Post not found");
  }

  // Only the author can delete
  if (post.authorId !== ctx.user.id) {
    throw new UnauthorizedException(
      "You can only delete your own posts",
      { postId, authorId: post.authorId }
    );
  }

  return this.delete(postId);
}
```

### Role-Based Permission Validation

```typescript theme={null}
@api()
async deleteUser(ctx: Context, userId: number) {
  if (!ctx.user) {
    throw new UnauthorizedException("Login required");
  }

  // Check admin permission
  if (!ctx.user.isAdmin) {
    throw new UnauthorizedException(
      "Only administrators can delete users",
      { requiredRole: "admin", currentRole: ctx.user.role }
    );
  }

  // Cannot delete yourself
  if (ctx.user.id === userId) {
    throw new BadRequestException("Cannot delete yourself");
  }

  return this.delete(userId);
}
```

### Complex Permission Validation

```typescript theme={null}
@api()
async publishPost(ctx: Context, postId: number) {
  if (!ctx.user) {
    throw new UnauthorizedException("Login required");
  }

  const post = await this.findById(postId);

  if (!post) {
    throw new NotFoundException("Post not found");
  }

  // Must be author or have editor permissions
  const isAuthor = post.authorId === ctx.user.id;
  const isEditor = ctx.user.role === "editor" || ctx.user.role === "admin";

  if (!isAuthor && !isEditor) {
    throw new UnauthorizedException(
      "You don't have permission to publish this post",
      {
        postId,
        authorId: post.authorId,
        currentUserId: ctx.user.id,
        currentRole: ctx.user.role,
        requiredCondition: "author or editor/admin role"
      }
    );
  }

  return this.update(postId, { status: "published" });
}
```

### Organization/Team Permission Validation

```typescript theme={null}
@api()
async addTeamMember(
  ctx: Context,
  teamId: number,
  newMemberId: number
) {
  if (!ctx.user) {
    throw new UnauthorizedException("Login required");
  }

  const team = await TeamModel.findById(teamId);

  if (!team) {
    throw new NotFoundException("Team not found");
  }

  // Only team admins can add members
  const membership = await TeamMemberModel.findOne({
    teamId,
    userId: ctx.user.id
  });

  if (!membership) {
    throw new UnauthorizedException(
      "You are not a team member",
      { teamId }
    );
  }

  if (membership.role !== "admin" && membership.role !== "owner") {
    throw new UnauthorizedException(
      "Only team administrators can add members",
      {
        teamId,
        currentRole: membership.role,
        requiredRole: "admin or owner"
      }
    );
  }

  return TeamMemberModel.create({
    teamId,
    userId: newMemberId,
    role: "member"
  });
}
```

### API Key Authentication

```typescript theme={null}
@api()
async getApiData(ctx: Context) {
  const apiKey = ctx.headers["x-api-key"];

  if (!apiKey) {
    throw new UnauthorizedException(
      "API key is required",
      { header: "x-api-key" }
    );
  }

  const validKey = await ApiKeyModel.findByKey(apiKey);

  if (!validKey) {
    throw new UnauthorizedException(
      "Invalid API key"
    );
  }

  if (validKey.expiresAt && validKey.expiresAt < new Date()) {
    throw new UnauthorizedException(
      "Expired API key",
      { expiresAt: validKey.expiresAt }
    );
  }

  if (!validKey.isActive) {
    throw new UnauthorizedException(
      "Inactive API key",
      { keyId: validKey.id }
    );
  }

  // Return API data
  return this.getDataForApiKey(validKey.id);
}
```

### Time-Based Access Control

```typescript theme={null}
@api()
async accessRestrictedResource(ctx: Context, resourceId: number) {
  if (!ctx.user) {
    throw new UnauthorizedException("Login required");
  }

  const subscription = await SubscriptionModel.findByUserId(ctx.user.id);

  if (!subscription) {
    throw new UnauthorizedException(
      "Subscription required for this resource",
      { resourceId }
    );
  }

  const now = new Date();

  // Check subscription expiration
  if (subscription.expiresAt < now) {
    throw new UnauthorizedException(
      "Subscription has expired",
      {
        expiresAt: subscription.expiresAt,
        renewUrl: "/subscription/renew"
      }
    );
  }

  // Access restrictions by subscription plan
  const resource = await ResourceModel.findById(resourceId);

  if (resource.requiredPlan === "premium" && subscription.plan === "basic") {
    throw new UnauthorizedException(
      "Premium plan required for this resource",
      {
        requiredPlan: "premium",
        currentPlan: "basic",
        upgradeUrl: "/subscription/upgrade"
      }
    );
  }

  return resource;
}
```

### IP-Based Access Control

```typescript theme={null}
@api()
async adminOnlyEndpoint(ctx: Context) {
  if (!ctx.user?.isAdmin) {
    throw new UnauthorizedException("Admin permission required");
  }

  // Allow access only from specific IP ranges
  const allowedIPs = ["192.168.1.0/24", "10.0.0.0/8"];
  const clientIP = ctx.request.ip;

  if (!this.isIPAllowed(clientIP, allowedIPs)) {
    throw new UnauthorizedException(
      "IP address not allowed",
      {
        clientIP,
        allowedRanges: allowedIPs
      }
    );
  }

  return this.getAdminData();
}

private isIPAllowed(ip: string, allowedRanges: string[]): boolean {
  // IP range check logic
  return true; // Actual implementation needed
}
```

## Guards vs Manual Check Comparison

### Using Guards (Recommended)

```typescript theme={null}
// sonamu.config.ts
guardHandler: (guard, request, api) => {
  if (guard === "user" && !request.user) {
    throw new UnauthorizedException("Login required");
  }

  if (guard === "admin" && !request.user?.isAdmin) {
    throw new UnauthorizedException("Admin permission required");
  }

  return true;
}

// API method
@api({ guards: ["user"] })
async simpleUserEndpoint(ctx: Context) {
  // Authentication check automatically completed
  return this.getData(ctx.user!.id);
}

@api({ guards: ["admin"] })
async adminEndpoint(ctx: Context) {
  // Admin permission check automatically completed
  return this.getAdminData();
}
```

### Manual Check (Complex Logic)

```typescript theme={null}
@api()
async complexAuthEndpoint(ctx: Context, postId: number) {
  if (!ctx.user) {
    throw new UnauthorizedException("Login required");
  }

  const post = await PostModel.findById(postId);

  // Complex permission logic: author, editor, or same team member
  const isAuthor = post.authorId === ctx.user.id;
  const isEditor = ctx.user.role === "editor";
  const isSameTeam = await TeamModel.areInSameTeam(
    ctx.user.id,
    post.authorId
  );

  if (!isAuthor && !isEditor && !isSameTeam) {
    throw new UnauthorizedException("Access denied");
  }

  return post;
}
```

## payload Usage Patterns

### Login Prompt

```typescript theme={null}
throw new UnauthorizedException("Login required", {
  loginUrl: "/auth/login",
  returnUrl: ctx.request.url,
});
```

### Permission Upgrade Prompt

```typescript theme={null}
throw new UnauthorizedException("Premium feature", {
  requiredPlan: "premium",
  currentPlan: "basic",
  upgradeUrl: "/subscription/upgrade",
  features: ["Advanced analytics", "Unlimited projects", "Priority support"],
});
```

### Expiration Information

```typescript theme={null}
throw new UnauthorizedException("Session has expired", {
  expiredAt: session.expiresAt,
  refreshUrl: "/auth/refresh",
});
```

## Client Response Examples

### Basic Response

```json theme={null}
{
  "statusCode": 401,
  "message": "Login required"
}
```

### Response with payload

```json theme={null}
{
  "statusCode": 401,
  "message": "Premium plan required for this resource",
  "payload": {
    "requiredPlan": "premium",
    "currentPlan": "basic",
    "upgradeUrl": "/subscription/upgrade"
  }
}
```

## 401 vs 403

* **401 Unauthorized**: When authentication itself fails or permissions are insufficient
* **403 Forbidden**: When authenticated but explicitly lacks permission to access the resource

In Sonamu, `UnauthorizedException` handles both, but you can create a custom `ForbiddenException` if needed.

```typescript theme={null}
// Custom 403 exception
export class ForbiddenException extends SoException {
  constructor(
    public message = "Forbidden",
    public payload?: unknown,
  ) {
    super(403, message, payload);
  }
}

// Usage
@api()
async deleteUser(ctx: Context, userId: number) {
  if (!ctx.user) {
    throw new UnauthorizedException("Login required");
  }

  if (!ctx.user.isAdmin) {
    throw new ForbiddenException(
      "You don't have permission to perform this action",
      { requiredRole: "admin" }
    );
  }

  return this.delete(userId);
}
```

## Related Documentation

* [SoException](/en/api-reference/exceptions/sonamu-error)
* [AuthContext](/en/api-reference/context/auth-context)
* [Guards](/en/api-development/guards)
* [Custom Exceptions](/en/api-reference/exceptions/custom-errors)
