resolveOAuthCallbackSignInForProvider<TContext, TProfile extends Object> function
Future<AuthOAuthCallbackSignInResolution>
resolveOAuthCallbackSignInForProvider<TContext, TProfile extends Object>({
- required AuthStore store,
- required TContext context,
- required OAuthProvider<
TProfile> provider, - required String code,
- required String? receivedState,
- required String stateKey,
- required String pkceKey,
- required String callbackKey,
- required String? readSession(
- String key
- required Client httpClient,
- String nonceKey = '_auth.nonce',
- void removeSession(
- String key
- FutureOr<
AuthOAuthChallenge?> consumeChallenge()?, - String? expectedBrowserState,
- bool requireBrowserState = false,
- String fallbackAccountId()?,
Resolves a full OAuth callback flow including state validation and account linking.
Implementation
Future<AuthOAuthCallbackSignInResolution>
resolveOAuthCallbackSignInForProvider<TContext, TProfile extends Object>({
required AuthStore store,
required TContext context,
required OAuthProvider<TProfile> provider,
required String code,
required String? receivedState,
required String stateKey,
required String pkceKey,
required String callbackKey,
required String? Function(String key) readSession,
required http.Client httpClient,
String nonceKey = '_auth.nonce',
void Function(String key)? removeSession,
FutureOr<AuthOAuthChallenge?> Function(String providerId, String state)?
consumeChallenge,
/// A browser-bound copy of the received state, supplied by the framework
/// adapter (for example from an HttpOnly state cookie).
String? expectedBrowserState,
/// Requires the adapter to provide a browser-bound state value. This keeps
/// a durable challenge store from accepting a callback initiated by another
/// browser.
bool requireBrowserState = false,
String Function()? fallbackAccountId,
}) async {
if (requireBrowserState) {
// Reject an unbound callback before consuming the durable challenge. An
// attacker must not be able to turn a failed login-CSRF attempt into a
// denial of service for the browser that started the OAuth flow.
ensureOAuthStateMatches(
expectedState: expectedBrowserState,
receivedState: receivedState,
);
}
final AuthOAuthCallbackSessionValues sessionValues;
if (consumeChallenge != null) {
final challenge = receivedState == null
? null
: await Future.sync(() => consumeChallenge(provider.id, receivedState));
if (challenge == null) {
throw AuthFlowException('invalid_state');
}
sessionValues = AuthOAuthCallbackSessionValues(
expectedState: challenge.state,
codeVerifier: challenge.codeVerifier,
nonce: challenge.nonce,
callbackUrl: challenge.callbackUrl,
);
// The durable challenge is the replay guard, but the mirrored framework
// state should be removed from the browser session once that challenge is
// consumed as well.
removeSession?.call(authProviderStateSessionKey(stateKey, provider.id));
} else {
sessionValues = resolveOAuthCallbackSessionValues(
providerId: provider.id,
stateKey: stateKey,
pkceKey: pkceKey,
nonceKey: nonceKey,
callbackKey: callbackKey,
readSession: readSession,
);
ensureOAuthStateMatches(
expectedState: sessionValues.expectedState,
receivedState: receivedState,
);
removeSession?.call(authProviderStateSessionKey(stateKey, provider.id));
removeSession?.call(authProviderPkceSessionKey(pkceKey, provider.id));
removeSession?.call(authProviderNonceSessionKey(nonceKey, provider.id));
removeSession?.call(
authProviderCallbackSessionKey(callbackKey, provider.id),
);
}
ensureOAuthStateMatches(
expectedState: sessionValues.expectedState,
receivedState: receivedState,
);
final signIn = await resolveOAuthSignInForProvider<TContext, TProfile>(
store: store,
context: context,
provider: provider,
code: code,
codeVerifier: sessionValues.codeVerifier,
oidcNonce: sessionValues.nonce,
httpClient: httpClient,
fallbackAccountId: fallbackAccountId,
);
return AuthOAuthCallbackSignInResolution(
signIn: signIn,
callbackUrl: sessionValues.callbackUrl,
);
}