Skip to content

Authentication Flow

Pedro Monteiro edited this page Jan 30, 2026 · 2 revisions

Our authentication system is designed to support multiple login methods (standard credentials and federated login via OpenID Connect) while providing a robust mechanism for session persistence and automatic token refreshing.

Architecture Overview

The authentication flow is built upon three core abstractions located in lib/session/flows/base/:

  1. SessionInitiator: Represents the intent to login. It collects necessary user input (like username/password or an OIDC token) and prepares a request.
  2. SessionRequest: Represents the action of authenticating. It executes the network calls to exchange credentials for a session.
  3. Session: Represents the state of being logged in. It holds the authentication artifacts (Cookies, Tokens, User metadata).

1. The Session Initiator

Located in lib/session/flows/base/initiator.dart. The UI (login.dart) does not perform login logic directly. Instead, it creates a specific SessionInitiator based on the user's choice:

  • CredentialsSessionInitiator: Takes username and password.
  • FederatedSessionInitiator: Takes an OIDC clientId and realm.
// Example usage in login.dart
final initiator = CredentialsSessionInitiator(
  username: user,
  password: pass,
);
await sessionNotifier.login(initiator, persistentSession: true);

2. The Session Request

The initiator produces a SessionRequest. When request.perform() is called, the app communicates with the Sigarra API or the Identity Provider.

  • Credentials Flow: Posts the username/password to the legacy HTML endpoints or API to get session cookies.
  • Federated Flow: Uses the openid_client library to perform the OAuth2 handshake.

3. The Session Object

If successful, a Session object is returned. This is the object that is serialized to local storage for "Remember Me" functionality. It MUST implement:

  • mainFaculty: The primary faculty of the user.
  • cookies: The HTTP cookies required for authenticated requests.
  • createRefreshRequest(): A crucial method that allows the AuthenticationController to re-login the user silently when their session expires.

The Authentication Controller

The AuthenticationController (lib/session/authentication_controller.dart) is the brain of the operation. It sits between the raw Session data and the rest of the application.

Why do we need it?

Sigarra sessions often expire or require refreshing. The controller ensures that:

  1. Concurrency Safety: Prevents multiple parts of the app from trying to refresh the session simultaneously (using synchronized locks).
  2. Snapshots: Provides AuthenticationSnapshots to API clients. A snapshot guarantees that the session is valid at the moment it was requested.
  3. Automatic Invalidation: If a network request fails with a 401/403, the controller automatically triggers _reauthenticate().

The Lifecycle

  1. Access: An API fetcher requests controller.snapshot.
  2. Usage: The fetcher uses snapshot.session to make a request.
  3. Failure: If the request fails due to AuthenticationException, the fetcher calls snapshot.invalidate().
  4. Recovery: The controller acquires a lock, calls currentSession.createRefreshRequest(), performs the handshake again, and notifies all listeners with the new valid session.

State Management Integration

The sessionProvider (Riverpod) acts as the glue.

  1. It holds the current Session? state.
  2. It initializes the NetworkRouter with the AuthenticationController upon login.
  3. It persists the session to SharedPreferences if persistentSession is true.

Flow Summary: UI -> Initiator -> SessionNotifier.login() -> Request.perform() -> Session -> AuthenticationController initialized.

Clone this wiki locally