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 and nonce.
Returns a Future<OAuthSession> containing the new access token, possibly a new refresh token, and updated session metadata.
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
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 endpoint = Uri.https(service, '/oauth/token');
final dPoPHeader = getDPoPHeader(
clientId: metadata.clientId,
endpoint: endpoint.toString(),
method: 'POST',
dPoPNonce: session.$dPoPNonce,
publicKey: session.$publicKey,
privateKey: session.$privateKey,
);
final response = await http.post(
endpoint,
headers: {
'DPoP': dPoPHeader,
},
body: {
'client_id': metadata.clientId,
'grant_type': 'refresh_token',
'refresh_token': session.refreshToken,
},
);
final body = jsonDecode(response.body);
if (body['error'] == 'use_dpop_nonce' &&
response.headers.containsKey('dpop-nonce')) {
session.$dPoPNonce = response.headers['dpop-nonce']!;
// Retry with next DPoP nonce
return await refresh(session);
}
if (response.statusCode != 200) {
throw OAuthException(response.body);
}
return OAuthSession(
accessToken: body['access_token'],
refreshToken: body['refresh_token'],
tokenType: body['token_type'],
scope: body['scope'],
expiresAt:
DateTime.now().toUtc().add(Duration(seconds: body['expires_in'])),
sub: body['sub'],
$dPoPNonce: response.headers['dpop-nonce']!,
$publicKey: session.$publicKey,
$privateKey: session.$privateKey,
);
}