DataConnectors
DataConnectors copied to clipboard
Failing to refresh token because of multiple requests to refresh authentication token using PKCE method
We have a custom connector that is using the PKCE OAuth2 flow based around the provided example here and we are having users report occasional issues where they need to sign in again on Power BI Desktop to refresh their data. In looking at API logs on our end, it appears we have some cases where we see repeat calls to use the same refresh token to fetch an updated authentication token in a short period. We speculate there is parallel loading of various tables in Power Query and multiple queries are trying to use the same refresh token to refresh the authentication token. The first attempt to use the refresh token is successful with a 200 response but all subsequent calls using the same refresh token receive a 400 status with an error message which breaks the refresh and the users have to login again.
Here is a snippet of the authentication from our connector:
StartLogin = (resourceUrl, state, display) =>
let
clientId = getClientIdByRegion(resourceUrl),
// We'll generate our code verifier using Guids
codeVerifier = Text.NewGuid() & Text.NewGuid(),
AuthorizeUrl = authorize_uri & "?" & Uri.BuildQueryString([
client_id = clientId,
response_type = "code",
code_challenge_method = "plain",
scope="",
code_challenge = codeVerifier,
state = state,
redirect_uri = redirect_uri])
in
[
LoginUri = AuthorizeUrl,
CallbackUri = redirect_uri,
WindowHeight = 720,
WindowWidth = 1024,
// Need to roundtrip this value to FinishLogin
Context = codeVerifier
];
// The code verifier will be passed in through the context parameter.
FinishLogin = (c, dataSourcePath, context, callbackUri, state) =>
let
Parts = Uri.Parts(callbackUri)[Query]
in
TokenMethod(dataSourcePath, Parts[code], "authorization_code", context);
TokenMethod = (dataSourcePath, code, grant_type, optional verifier) =>
let
// region = Record.Field(dataSourcePath, "region"),
clientId = getClientIdByRegion(dataSourcePath),
codeVerifier = if (verifier <> null) then [code_verifier = verifier] else [],
codeParameter = if (grant_type = "authorization_code") then [ code = code ] else [ refresh_token = code ],
query = codeVerifier & codeParameter & [
client_id = clientId,
grant_type = grant_type,
redirect_uri = redirect_uri
],
ManualHandlingStatusCodes= {},
Response = Web.Contents(base_path & "/authentication" & "/token", [
Content = Text.ToBinary(Uri.BuildQueryString(query)),
Headers = [
#"Content-type" = "application/x-www-form-urlencoded",
#"Accept" = "application/json"
],
ManualStatusHandling = ManualHandlingStatusCodes
]),
Parts = Json.Document(Response)
in
// check for error in response
if (Parts[error]? <> null) then
error Error.Record(Parts[error], Parts[message]?)
else
Parts;
Refresh = (ca, resourceUrl, oldCredentials) => TokenMethod(resourceUrl, oldCredentials[refresh_token], "refresh_token");
How can we ensure there is only one call to refresh the authentication token when there are multiple queries that depend on the authentication token?