begin method
The first half: discover, register, and build the authorization URL.
keepScopes are scopes already held that a step-up must not lose.
Implementation
Future<({Uri url, PendingAuthorization pending})> begin({
required String serverUrl,
String? wwwAuthenticate,
List<String> keepScopes = const [],
}) async {
final resourceUrl = canonicalResourceUri(serverUrl);
final challenge = WwwAuthenticateChallenge.parse(wwwAuthenticate);
final prm = await _protectedResource(serverUrl, challenge);
final resource = prm == null ? resourceUrl : canonicalResourceUri(prm.resource);
final metadata = prm == null
? await _legacyAuthorizationServer(serverUrl)
: await authorizationServerMetadata(_chooseAuthorizationServer(prm));
_requirePkce(metadata);
_requireHttps(metadata);
// A token from a different authorization server is never sent again.
final held = await options.store.loadGrant(resource);
if (held != null && held.issuer != metadata.issuer) {
_logger.warning('The authorization server of $resource changed (${held.issuer} -> ${metadata.issuer})');
await options.store.deleteGrant(resource);
}
final registration = await _registration(metadata);
final scopes = {
...keepScopes,
...(challenge?.scopes.isNotEmpty ?? false) ? challenge!.scopes : (prm?.scopesSupported ?? const <String>[]),
}.toList();
final verifier = _randomToken(32);
final state = _randomToken(16);
final url = Uri.parse(metadata.authorizationEndpoint).replace(queryParameters: {
...Uri.parse(metadata.authorizationEndpoint).queryParameters,
'response_type': 'code',
'client_id': registration.clientId,
'redirect_uri': options.redirectUri,
'code_challenge': base64Url.encode(sha256.convert(utf8.encode(verifier)).bytes).replaceAll('=', ''),
'code_challenge_method': 'S256',
'state': state,
'resource': resource,
if (scopes.isNotEmpty) 'scope': scopes.join(' '),
});
return (
url: url,
pending: PendingAuthorization(
resource: resource,
issuer: metadata.issuer,
issParameterSupported: metadata.authorizationResponseIssParameterSupported,
tokenEndpoint: metadata.tokenEndpoint,
clientId: registration.clientId,
redirectUri: options.redirectUri,
state: state,
codeVerifier: verifier,
scopes: scopes,
),
);
}