# Provider Deferred Required Markers and Session Lifetime Design

Date: 2026-08-03

## Summary

The provider portal will reveal a required-field marker only after a user tries
to submit a form with that field empty. The marker will disappear when the
field becomes valid. This behavior supersedes the always-visible marker rule in
the earlier provider form requirements guidance design; requirement guides and
accessible required semantics remain visible from the start.

The provider portal will also support a remembered login for exactly 30 days.
It will continue to authenticate through the classic `/auth/login` endpoint,
but the API will issue a context-specific JWT whose expiration matches the web
session. Expiration or logout affects only the API session created for that
browser login. Sessions belonging to the mobile app or other devices remain
active.

## Goals

- Hide visible required asterisks before the first invalid submission.
- Reveal asterisks only for required controls or required groups that are
  currently missing a value.
- Remove a marker once its requirement is satisfied.
- Preserve `required`, `aria-required`, labels, localized guidance, and field
  errors.
- Keep ordinary provider web sessions at the API default TTL of 60 minutes.
- Keep remembered provider web sessions active for 30 days (43,200 minutes).
- Preserve the existing app remembered-session TTL.
- Expire and revoke only the API session associated with the provider browser.
- Revoke the associated API session during an explicit provider logout.
- Preserve the same behavior through two-factor authentication and token
  refresh.

## Non-goals

- Changing the required fields or provider publication rules.
- Removing requirement guides shown before submission.
- Changing mobile-app session duration.
- Revoking other devices when a provider web session expires or logs out.
- Sharing JWTs with browser JavaScript.
- Introducing a separate provider user account or login endpoint.

## Required Marker Behavior

### Initial state

Required controls retain their native `required` attribute and receive
`aria-required="true"` when initialized. No visible asterisk or extra
screen-reader marker is inserted at page load. The form-level guide remains the
advance explanation of required fields.

### Invalid submission

The portal listens for native `invalid` events during capture because an
invalid form does not dispatch a normal `submit` event. When a required control
has `validity.valueMissing === true`, the portal inserts one coral asterisk and
one localized screen-reader description in its associated label. Duplicate
markers are not allowed.

Format errors on a non-empty value continue to use the existing field-error
presentation and do not add a required asterisk.

### Correction

On `input` and `change`, a marker is removed as soon as the control no longer
has a missing-value error. Dynamically created and conditionally required
controls use the same functions and lifecycle.

### Required groups

Requirements that cannot use a single native `required` control, such as the
category selection and primary category, expose the same marker through an
explicit group helper. The marker is hidden initially, revealed after a failed
save caused by the missing group selection, and removed after a valid selection
is made.

## Authentication Contract

### Login context

`web_mycode` continues to post credentials to `/auth/login` through its
server-side BFF. The BFF adds `session_context=provider_web`; it is not accepted
from or exposed to browser JavaScript as a configurable TTL.

The API validates the optional context against an allow-list. Supplying
`provider_web` can only shorten a remembered token, so it grants no additional
privilege.

### API lifetime policy

The API adds `JWT_PROVIDER_WEB_REMEMBER_TTL`, defaulting to `43200` minutes.
Token lifetime selection is:

| Context | Remember | TTL source |
|---|---:|---|
| `provider_web` | true | `JWT_PROVIDER_WEB_REMEMBER_TTL` (30 days) |
| `provider_web` | false | `JWT_TTL` (60 minutes) |
| absent/app | true | `JWT_REMEMBER_TTL` (existing app value) |
| absent/app | false | `JWT_TTL` |

The selected context is carried in the temporary two-factor token and in the
JWT claim so 2FA completion and refresh cannot silently expand a 30-day web
session into the app remembered lifetime.

The generated JWT `exp` and the matching `user_sessions.expires_at` are the
authoritative expiration. Once they pass, API middleware already treats that
single row as inactive. No other session row is modified.

### Web session lifetime

`web_mycode` uses a 43,200-minute Laravel session retention window so its
server-side session and cookie do not discard a valid remembered web JWT first.
The JWT remains the authentication authority: an unremembered token is rejected
after 60 minutes even if the inert Laravel session cookie still exists.

The browser never receives the JWT. The web session continues to store it only
server-side.

### Logout

On explicit provider logout, the BFF calls `/user/logout` with the server-held
JWT before clearing and invalidating the local Laravel session. The API revokes
only the `session_id` contained in that token. Other app and browser sessions
remain active.

If the API token has already expired, the local session is still cleared and
the portal returns a successful local logout. No token or upstream diagnostic
is exposed to the browser.

## Error Handling and Security

- The API rejects unknown `session_context` values.
- Client input cannot specify an arbitrary TTL.
- JWT expiration remains enforced by the API and the web's existing token
  validator.
- Logout failures are normalized; secrets and bearer tokens are never returned.
- Invalid form behavior uses text and accessibility semantics in addition to
  color.
- Existing duplicate-submit protection remains unchanged.

## Testing Strategy

### API

- A remembered `provider_web` login returns `expires_in=43200*60` and stores a
  matching session expiration.
- A normal provider web login still uses `JWT_TTL`.
- An ordinary app remembered login still uses `JWT_REMEMBER_TTL`.
- Unknown contexts fail validation.
- Two-factor completion preserves the provider web context and TTL.
- Refresh preserves the provider web context and TTL.
- Expiring or logging out the web token leaves other user sessions active.

### Web

- The provider login forwards `session_context=provider_web` only from the BFF.
- The remembered session cookie is configured for 43,200 minutes.
- Explicit logout sends the stored bearer token to `/user/logout`, sanitizes
  the response, and clears local state.
- Required markers are absent on initialization.
- A missing required control gains one marker after invalid submission.
- Completing that control removes the marker.
- Non-empty format errors do not display a missing-required marker.
- Category group markers follow the same submit/recovery behavior.
- Spanish and English accessible marker text remain equivalent.

### Regression

- Run focused API authentication tests and the full API suite.
- Run provider JavaScript tests and provider feature tests.
- Run the complete web PHPUnit suite and strict architecture audit.
- Build production web assets and confirm generated bundles are consistent.

## Deployment

Deploy the API before the web so `session_context=provider_web` is understood
when the web begins sending it. Production environment configuration requires:

```dotenv
# api_mycode
JWT_PROVIDER_WEB_REMEMBER_TTL=43200

# web_mycode
SESSION_LIFETIME=43200
```

Both applications must clear and rebuild configuration caches after updating
their environment files.

## Acceptance Criteria

1. No provider required asterisk is visible before an invalid submission.
2. Only empty required fields or groups reveal an asterisk after submission.
3. Correcting the missing value removes its asterisk.
4. Requirement guides and accessible required semantics remain available.
5. Remembered provider web JWTs expire after exactly 30 days.
6. Unremembered provider web JWTs retain the ordinary API TTL.
7. Existing app remembered JWTs retain their existing configured TTL.
8. 2FA and refresh preserve the provider web lifetime.
9. Expiration or logout affects only the corresponding provider web API
   session.
10. JWTs remain server-side and all new behavior is covered by automated tests.
