refresh method
Refreshes an OAuth 2.0 access token using DPoP-bound refresh token flow.
This method exchanges a refresh token for a new access token while maintaining the DPoP binding. It reuses the original DPoP key pair and handles nonce updates from the authorization server.
session The current OAuthSession containing the refresh token
and DPoP credentials to be used for token refresh. The session must
include valid DPoP keys. The given session instance is never
modified; a brand-new OAuthSession is returned instead.
Returns a Future<OAuthSession> containing the new access token, possibly a new refresh token, and updated session metadata. If the authorization server does not rotate refresh tokens, the existing refresh token is carried over to the new session.
The DPoP keys are preserved from the original session while the nonce may be updated.
Throws:
- OAuthException in the following cases:
- When no refresh token is available in the session
- When the token refresh request fails
- When the server returns an error response
Example:
final newSession = await oauth.refresh(currentSession);
print('New access token: ${newSession.accessToken}');
The method maintains DPoP proof-of-possession by:
- Reusing the DPoP key pair from the original session
- Creating a new DPoP proof header for the refresh request
- Updating the DPoP nonce if provided in the response, with the
use_dpop_nonceretry bounded to a small number of attempts
The returned OAuthSession preserves the DPoP binding by:
- Keeping the same $publicKey and $privateKey
- Updating the $dPoPNonce if provided by the server
- Maintaining the DPoP-bound token type
Implementation
Future<OAuthSession> refresh(final OAuthSession session) async {
if (session.refreshToken.isEmpty) {
throw OAuthException('No refresh token available');
}
final serverMetadata = await _resolveServerMetadata();
final endpoint = Uri.parse(
serverMetadata.tokenEndpoint ?? '$_origin/oauth/token',
);
final result = await _postTokenRequest(
endpoint: endpoint,
initialDPoPNonce: session.$dPoPNonce,
publicKey: session.$publicKey,
privateKey: session.$privateKey,
bodyParams: {
'client_id': session.$clientId ?? metadata.clientId,
'grant_type': 'refresh_token',
'refresh_token': session.refreshToken,
},
);
return _buildSession(
result: result,
publicKey: session.$publicKey,
privateKey: session.$privateKey,
// Servers that do not rotate refresh tokens omit `refresh_token`
// from the response; keep using the existing one in that case.
fallbackRefreshToken: session.refreshToken,
fallbackSub: session.sub,
// A refresh response may omit `scope`; fall back to the already
// validated scope of the session being refreshed.
fallbackScope: session.scope,
// The refreshed tokens must belong to the same account the session
// was minted for.
expectedSub: session.sub,
);
}