Skip to content

Not authenticated for an APIRouteAuthenticatedRule: 401 (WWW-Authenticate: Bearer), not 403 (v1.29.0) - #195

Merged
gmpassos merged 3 commits into
masterfrom
feat/unauthenticated-401
Oct 28, 2026
Merged

gmpassos merged 3 commits into
masterfrom
feat/unauthenticated-401

Conversation

@gmpassos

@gmpassos gmpassos commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Why

A request refused by a route's rules was always answered 403, whether it was not authenticated (no credential, or an invalid or expired token) or authenticated but refused. A client couldn't tell a lost login from a refused access.

Also, the refusal message was a payloadDynamic, which is dropped for a route that doesn't return a String. The client then got shelf's default body, Forbidden.

Changes

  • APIRouteHandler.call: when the rules refuse a request that isn't authenticated, and any of the route's APIRouteAuthenticatedRules (or subclasses, like APIRoutePermissionTypeRule) refuses it, the response requires authentication. This holds whatever the order of the rules: a route's own rules come before its module's global ones, so another rule may refuse it first. The response is:
    • status 401
    • header WWW-Authenticate: Bearer realm="API"
    • body UNAUTHORIZED: Not authenticated, sent as error, so it is kept whatever the route returns
    • Bearer, not the default Basic: browsers show no username/password prompt.
  • Any other refusal by a route's rules is still 403 (UNAUTHORIZED: Rules issues ...).
  • New API:
    • APIRouteHandler.isUnauthenticatedRefusal(request).
    • APIRouteAuthenticatedRule.isAuthenticated(request).
  • Fix (APIServer): a blank authenticationRealm now defaults to API. It checked the authentication type instead.

Compatibility

  • HTTP clients: a client that treated any 403 from a rule refusal as "not logged in" now also needs to handle 401.
  • Direct callers (APIRoot.call / doCall): for this refusal the message is now the APIResponse.error; it used to be the payload. The response also has requiresAuthentication set, with authenticationType Bearer.

Tests

  • bones_api_cov_server_http_test.dart:
    • no credential and an invalid token → 401, WWW-Authenticate: Bearer realm="API", error body
    • a non-authentication rule's refusal → 403, no WWW-Authenticate
    • not authenticated, with the authentication rule after a rule that refuses it first → still 401
    • a blank realm → API
  • bones_api_cov_root_test.dart: the not-authenticated refusal requires Bearer authentication.
  • Full VM suite: 2688 passed, 12 skipped. The only failure was ensure_build, which fails on a dirty tree; it passes after the commit.

🤖 Generated with Claude Code

gmpassos and others added 3 commits October 28, 2026 06:13
…cate: Bearer), not 403 (v1.29.0)

A client can tell a lost login from a refused access. Also: a blank
authenticationRealm defaults to API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rror in the CHANGELOG

isNotAuthenticatedRefusal checks all of a route's APIRouteAuthenticatedRules,
not only the first rule refusing the request: a route's own rules come before
its module's global ones. failedRule removed (no longer needed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gmpassos
gmpassos merged commit 54446da into master Oct 28, 2026
9 checks passed
@codecov

codecov Bot commented Oct 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.68%. Comparing base (f0bb3b2) to head (74e5bca).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #195   +/-   ##
=======================================
  Coverage   95.67%   95.68%           
=======================================
  Files          75       75           
  Lines       25775    25784    +9     
=======================================
+ Hits        24661    24671   +10     
+ Misses       1114     1113    -1     
Flag Coverage Δ
unittests 95.68% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant