public async Task ClaimTokenAsync(string accountLinkingToken, string tenantId, string userId, string codeVerifier) { var(acctState, exp) = await _oAuthStateService.GetAsync(accountLinkingToken); // ensure the PKCE verifier is correct var codeGuess = Pkce.Base64UrlEncodeSha256(codeVerifier); if (!string.Equals(acctState.CodeChallenge, codeGuess, StringComparison.OrdinalIgnoreCase)) { _logger.LogWarning("PKCE verification failed:\nChallenge:{codeChallenge}\nVerifier:{verifier}\nH(Verifier):{guess}", acctState.CodeChallenge, codeVerifier, codeGuess); throw new Exception("PKCE code verification failed"); } // ensure this isn't a replay await _replayValidator.ClaimIdAsync(acctState.Id, exp); // Claim the oauth code from the downstream var oAuthResult = await _oAuthServiceClient.ClaimCodeAsync(acctState.OAuthCode); var dto = new OAuthUserTokenDto { AccessToken = oAuthResult.AccessToken, AccessTokenExpiration = DateTimeOffset.Now + TimeSpan.FromSeconds(oAuthResult.ExpiresInSeconds), RefreshToken = oAuthResult.RefreshToken }; var serializedDto = JsonSerializer.Serialize(dto); await _userTokenStore.SetTokenAsync(tenantId : tenantId, userId : userId, serializedDto); }
public override async Task <DialogTurnResult> ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default) { dc = dc ?? throw new ArgumentNullException(nameof(dc)); var userId = dc.Context.Activity.From.AadObjectId; var tenantId = dc.Context.Activity.Conversation.TenantId; // Check for timeout var state = dc.ActiveDialog.State; var expires = (DateTimeOffset)state[ExpirationKey]; var isMessage = dc.Context.Activity.Type == ActivityTypes.Message; // If the incoming Activity is a message, or an Activity Type normally handled by OAuthPrompt, // check to see if this OAuthPrompt Expiration has elapsed, and end the dialog if so. var isTokenResponse = IsTokenResponseEvent(dc.Context); var isTimeoutActivityType = isMessage || IsTokenResponseEvent(dc.Context); var hasTimedOut = isTimeoutActivityType && DateTimeOffset.UtcNow >= expires; if (hasTimedOut) { _logger.LogWarning("User completed logout after timeout, bailing on dialog"); // if the token fetch request times out, complete the prompt with no result. return(await dc.EndDialogAsync(cancellationToken : cancellationToken).ConfigureAwait(false)); } if (isTokenResponse) { _logger.LogInformation("Detected token response, attempting to complete auth flow"); var value = dc.Context.Activity.Value as JObject; var stateString = value?.GetValue("state")?.ToString() ?? string.Empty; var authResponse = JsonSerializer.Deserialize <AuthResponse>(stateString); var codeVerifier = state[CodeVerifierKey] as string ?? string.Empty; var expectedState = Pkce.Base64UrlEncodeSha256(codeVerifier); if (!string.Equals(authResponse?.State, expectedState)) { // The state returned doesn't match the expected. potentially a forgery attempt. _logger.LogWarning("Potential forgery attempt: {expectedState} | {actualState} | {verifier}", expectedState, authResponse?.State, codeVerifier); return(await dc.EndDialogAsync(cancellationToken : cancellationToken).ConfigureAwait(false)); } await _oauthTokenProvider.ClaimTokenAsync( accountLinkingToken : authResponse?.AccountLinkingState ?? string.Empty, tenantId : tenantId, userId : userId, codeVerifier : codeVerifier ); } var tokenResult = await _oauthTokenProvider.GetAccessTokenAsync(tenantId : tenantId, userId : userId); if (tokenResult is NeedsConsentResult) { _logger.LogWarning("User failed to consent, bailing on dialog"); return(await dc.EndDialogAsync(cancellationToken : cancellationToken).ConfigureAwait(false)); } else if (tokenResult is AccessTokenResult accessTokenResult) { var activityId = state[CardActivityKey] as string; if (activityId != default) { // Since the signin "state" is only good for one login, we need to ensure the card for 'login' is overwritten var activity = MessageFactory.Attachment(new Attachment { ContentType = HeroCard.ContentType, Content = new HeroCard(title: "You are now logged in") }); activity.Id = activityId; await dc.Context.UpdateActivityAsync(activity, cancellationToken : cancellationToken); } return(await dc.EndDialogAsync(accessTokenResult, cancellationToken).ConfigureAwait(false)); } return(EndOfTurn); }