# Authorization

Guard routes and RPCs with allow or deny checks using noop, RBAC, or Casbin.

`permission` answers what a caller may do through `Can(subject, action, resource)`, returning `allow`, `implicit_deny`, or `explicit_deny`. Adapters: `noop` (`permission/noop`, default, denies all), `rbac` (`permission/rbac`), `casbin` (`permission/casbin`); identity comes from [Authentication](https://zenta-dev.github.io/zever/guides/security/auth/).

## Quick start

The default `noop` denies every check with `implicit_deny`: a placeholder until real policies exist, never for a protected deployment.

```sh
PERMISSION_ADAPTER=rbac
```

```sh
PERMISSION_ADAPTER=rbac
```

```go title="main.go"
p, err := c.Permission()
if err != nil {
    return err // config invalid or policy files unreadable
}
```

`rbac` does in-memory role checks from `rules` (`role`, `action`, `effect`, `owned_only`) and `roles`; `casbin` reads `model_path` and `policy_path` (set together, and `rules` must then be empty) ([reference](https://zenta-dev.github.io/zever/reference/config/#permission)).

## Guard an HTTP route or gRPC call

`authz` has no container accessor; it composes `Auth()` and `Permission()`. HTTP takes a `Policy` and a resource-ID extractor, gRPC a method-policy map.

```go title="guard.go"
authn, err := c.Auth()
if err != nil {
    return err
}
checker, err := c.Permission()
if err != nil {
    return err
}

pol := authz.Policy{AuthRequired: true, PermissionCheck: "spaces.read", ResourceType: "space"}
guarded := authz.Middleware(authn, checker, pol, func(r *http.Request) string {
    return r.PathValue("id")
})(handler)

policies := map[string]authz.Policy{
    "/booking.BookingService/Book": {AuthRequired: true, PermissionCheck: "bookings.write", ResourceType: "booking"},
}
guardedUnary := authz.UnaryServerInterceptor(authn, checker, policies)
```

Valid-token, matching-grant requests reach the handler; others are denied. A `Can` error means the system failed, so treat it as a denial. gRPC reads the resource id from `ResourceIDer`; `GetId` (not `GetID`) matches the `protoc-gen-go` getter, so generated request types satisfy it:

```go
type ResourceIDer interface {
    GetId() string
}
```

Decisions are `allow`, `implicit_deny` (no rule matched), or `explicit_deny` (a rule refused); the last two, plus unknown roles and missing policies, fail closed. `model_path` and `policy_path` reject `..` and must exist at `Open`.

## Production checklist

- Replace `noop` before any protected deployment and audit the adapter value.
- Deny on unknown answers and checker errors too.
- Never add a method to the public `Checker` interface; add a smaller one.
- Keep policy files in version control and sync them across instances.

## Troubleshooting

<details>
<summary>Every request is denied</summary>

The adapter is still `noop`. Set `PERMISSION_ADAPTER=rbac` (or `casbin`).
</details>

<details>
<summary>`Open` fails on policy paths</summary>

The path uses `..` or does not exist; use a repo-relative path that exists at startup.
</details>

<details>
<summary>Denials for valid roles</summary>

The role or resource differs from the policy (case, prefix, plural); compare exact `Can` arguments with the policy file.
</details>

<details>
<summary>`Can` errors under load</summary>

The backing store is unreachable; treat it as a denial and check connectivity.
</details>

## Related

[Authentication](https://zenta-dev.github.io/zever/guides/security/auth/) · [Adapters matrix](https://zenta-dev.github.io/zever/reference/adapters-matrix/)
