Skip to content

Authentication

Sizzle Starter revolutionizes app development with its secure, efficient authentication process. Drawing inspiration from fresh, it offers an enhanced design for effortless integration into any system.

Token-based authentication

Token-based is a popular approach to secure APIs and manage authorization. This approach relies on two types of tokens — Access and Refresh.

Access tokens, passed in the Authorization HTTP header, authenticate requests for actions like sending money or posting on secure routes. Their short lifespan enhances security by reducing theft risks. In contrast, refresh tokens, which don’t allow route access, renew these access tokens and usually last over 30 days.

Token Storage

Tokens need to be saved across sessions to avoid repeated credential entries by users. For this, Sizzle Starter offers the TokenStorage interface:

abstract interface class TokenStorage<T> {
/// Loads tokens.
Future<T?> loadTokenPair();
/// Save the Auth token pair.
Future<void> saveTokenPair(T tokenPair);
/// Clear the Auth token pair.
Future<void> clearTokenPair();
/// A stream of token pairs.
Stream<T?> getTokenPairStream();
/// Close the token storage.
Future<void> close();
}

When a new token is acquired during signIn or signUp, it’s stored in memory via saveTokenPair. This token can be retrieved using loadTokenPair. If the refresh token expires, the pair must be cleared using clearTokenPair.

The getTokenPairStream method is crucial for notifying about storage changes. For instance, saving a new token pair triggers a new notification to this stream.

Refresh Client

The Refresh Client in Sizzle Starter automates the renewal of tokens, ensuring continuous access:

/// The client that refreshes the Auth token using the refresh token.
///
/// This client is used by the [AuthInterceptor] to refresh the Auth token.
abstract interface class RefreshClient<T> {
/// Refresh the Auth token.
///
/// This method is called by the [AuthInterceptor]
/// when the request fails with a 401.
Future<T> refreshToken(T token);
}

Auth interceptor

The Auth Interceptor is pivotal in managing authentication. It automatically adds access tokens to requests and handles token renewal.

The Interceptor adjusts all outgoing requests to include an access token from storage. It also monitors response status codes, refreshing the token and retrying the request if it encounters a 401 status.

Per the specification, a [401 Unauthorized] status code indicates an authentication issue, typically due to an expired access token or its absence.

Usage

Adding an Auth interceptor in Dio follows the same process as other interceptors:

// Create an instance of Dio
final interceptedDio = Dio();
// Configure AuthInterceptor with tokenStorage and refreshClient
final authInterceptor = AuthInterceptor(
storage: tokenStorage,
refreshClient: refreshClient,
);
// Add AuthInterceptor to Dio’s interceptors
interceptedDio.interceptors.add(authInterceptor);

Additionally, implementing tokenStorage and refreshClient is necessary. For an example of an application built using this interceptor, see Auther.

Use RevokeTokenException in RestClient to signal a failed refresh request caused by an expired or malformed token. This triggers the interceptor to remove the token from memory.

For data sources needing authentication, use this dio instance for network requests.

Interceptor Design

Designed for efficiency, the Interceptor in Sizzle Starter ensures synchronized authentication across your application. It also implements AuthDataSource interface.

/// AuthSource provides valuable information about the authentication state
abstract interface class AuthStatusDataSource {
/// Stream of token pairs
///
/// This stream should be listened from repository and bloc,
/// if it emits null, it means the token pair is revoked
/// and the user should be logged out.
Stream<AuthenticationStatus> getAuthenticationStatusStream();
}

The interceptor implements Dio’s QueuedInterceptor interface to queue all requests and responses, ensuring that if one requires a token refresh, others wait without initiating new refresh requests.

It serves as the primary information source for high-level modules, enabling them to respond to authentication changes by emitting relevant new states as required.

Upon initialization, the interceptor loads and caches the token pair from storage. It also monitors the storage’s token stream to keep the cache updated. This ensures synchronization, as not all token operations involve the interceptor. For instance, when a user signs out and the token pair is cleared from storage, the interceptor will automatically update its cache to reflect this change.