# Keep authentication intact when agents change your API

Check that private routes still require authentication and records still require permission.

Source: https://perchscan.com/examples/endpoint-security/
Published: 2026-09-20

```yaml
- name: profile-is-not-public
  where: sample/19-auth-jwt/src/auth/auth.controller.ts
  ensure: >-
    In AuthController, signIn may be public and getProfile
    must require the global authentication guard. Marking
    getProfile or its containing class Public breaks this
    rule.
```

This rule catches one specific mistake: making a private profile endpoint public by accident. It is written for the [NestJS JWT example](https://github.com/nestjs/nest/tree/14b74ae705904626aa2bbe8ee30ad94f48d16381/sample/19-auth-jwt). The Python example further down checks who owns each record.

Both rules are written from each project's source. Neither is a report from a Perch scan of that project.

## Python: FastAPI ownership checks

In the [FastAPI template's item routes](https://github.com/fastapi/full-stack-fastapi-template/blob/cb740b656d7a0a6c5e12c7bf8e50343ec94ee9c7/backend/app/api/routes/items.py), ordinary users can reach only their own items, and superusers can reach more. The create handler sets the owner to the authenticated user. For ordinary users, the list query limits both the results and the count to items they own.

Clone that revision:

```sh
git clone https://github.com/fastapi/full-stack-fastapi-template.git fastapi-example
cd fastapi-example
git checkout cb740b656d7a0a6c5e12c7bf8e50343ec94ee9c7
```

Save this as `perch.yaml` in the root of the clone:

```yaml
- name: item-access-requires-owner
  where: backend/app/api/routes/items.py
  each: method
  ensure: >-
    An item endpoint derives the acting user from CurrentUser.
    Non-superusers may read, change, or delete only their own
    items. Listing applies the owner restriction to both the
    records and the count. Creating an item assigns the
    authenticated user's ID as owner. Trusting a caller-supplied
    owner ID or accepting an item ID without the ownership
    check breaks this rule.
```

Scan with only the custom rules, then check a single handler after you edit it:

```sh
perch scan --paths backend/app/api/routes/items.py --filter type=lint
perch check backend/app/api/routes/items.py::update_item \
  --rules item-access-requires-owner
```

`CurrentUser` is defined through a dependency in [deps.py](https://github.com/fastapi/full-stack-fastapi-template/blob/cb740b656d7a0a6c5e12c7bf8e50343ec94ee9c7/backend/app/api/deps.py). Review that dependency along with the endpoint: a parameter named `current_user` does not by itself show that authentication works.

## TypeScript: NestJS guard registration

The [NestJS JWT sample](https://github.com/nestjs/nest/tree/14b74ae705904626aa2bbe8ee30ad94f48d16381/sample/19-auth-jwt) registers `AuthGuard` with `APP_GUARD`, so the guard runs on every route. The login handler is marked public. The profile handler reads the user from the request, which the guard has already authenticated by verifying a bearer token.

```sh
git clone --filter=blob:none --sparse https://github.com/nestjs/nest.git nest-example
cd nest-example
git checkout 14b74ae705904626aa2bbe8ee30ad94f48d16381
git sparse-checkout set sample/19-auth-jwt
```

Save these file rules as `perch.yaml` in the root of the clone. Registering the guard and exempting public routes happen in different files, so each gets its own check:

```yaml
- name: auth-guard-is-global
  where: sample/19-auth-jwt/src/auth/auth.module.ts
  ensure: >-
    The providers declaration registers AuthGuard using
    APP_GUARD. Importing the guard without registering it,
    or registering it only as an ordinary injectable, breaks
    this rule.

- name: profile-is-not-public
  where: sample/19-auth-jwt/src/auth/auth.controller.ts
  ensure: >-
    In AuthController, signIn may be public and getProfile
    must require the global authentication guard. Marking
    getProfile or its containing class Public breaks this
    rule.

- name: guard-verifies-token
  where: sample/19-auth-jwt/src/auth/auth.guard.ts
  ensure: >-
    In canActivate, a route without the explicit public
    marker requires a bearer token verified by JwtService.
    Missing or invalid tokens are rejected. Merely decoding
    a token or allowing the request after verification
    throws breaks this rule.
```

```sh
perch scan --paths sample/19-auth-jwt/src/auth --filter type=lint
perch check sample/19-auth-jwt/src/auth/auth.controller.ts \
  --rules profile-is-not-public
```

These checks keep the sample's authentication wiring in place. Checking who owns a resource needs its own policy, like the FastAPI rule above. The [NestJS authentication documentation](https://docs.nestjs.com/security/authentication) explains global guards and explicit public routes.

## Authorization tests

Call each endpoint without signing in, as the resource owner, as another user, and as each administrative role that should have access. For list routes, check both the records returned and the count. A denied write must leave the resource unchanged.

Frameworks often apply authentication through dependency injection, decorators, or middleware, which can sit outside a method's static call graph. Keep the wiring checks separate, look at the context Perch supplied, and run HTTP tests to confirm the real access boundary. Review every model finding; a passing rule is not a security guarantee.
