Forum Discussion
Custom Connector failing on refreshing data with multiple calls to get a refresh token
Yes, here is a Fiddler snapshot of our authentication calls. First one is always 200 but the other calls receive a 400 because the refresh token can only be used once. There is one authentication call for each query from our connector even though the same access token can be used for all queries, Power BI seems to run each query in parallel including the authentication.
can't tell you filtered the between 149 and 152 and
take a look into the response body of the 200 request it should look like this:
{
"token_type":"Bearer",
"scope":"....",
"expires_in":5332,
"ext_expires_in":5332,
"access_token":"<token>"}
--> There is no request limit only a expirantion time.
and in the 400 request it should give you an oauth error in the return header.
normally if the token is expired your request should look like this:
- JoeFields4 years agoFrequent Visitor
We have a similar 200 response which contains the access_token, expires_in <seconds>, and refresh_token.
The issue is the new refresh_token is one-time use where as it appears in your case the refresh_token might be re-usable within a narrow window of time.
Here is the 400 response:
I am looking to see if there is anything in the connector code that we can do to limit the refresh to only one thread/instance or if we can ignore a 400 response as long as we have one 200 response with an updated access_token.
- AlexZak4 years agoFrequent Visitor
from Microsoft oauth flow - return of a new token refresh:
refresh_token A new OAuth 2.0 refresh token. Replace the old refresh token with this newly acquired refresh token to ensure your refresh tokens remain valid for as long as possible.
Note: Only provided if offline_access scope was requested.When you refresh your token a new token is generated that is the default.
i tried on my side and the BPI is sending and saving the correct token.
try to implement the advanced OAuth Handling authentication for Power Query connectors - Power Query | Microsoft Docs
here is my implementation
redirect_uri = "https://oauth.powerbi.com/views/oauthredirect.html"; // needs to be added as valid redirect URL (Desktop App --> PKCE!) logout_uri = "https://login.microsoftonline.com/logout.srf"; baseAuthentificationURL = (tenant) => "https://login.microsoftonline.com/" & tenant & "/oauth2/v2.0"; windowWidth = 1200; windowHeight = 1000; //OpenID method to get token endpoints on demand. openIDconfig = (dataSourcePath) => let //host = Uri.Parts(dataSourcePath)[Host], queryURL = "https://login.microsoftonline.com/" & Settings(dataSourcePath)[tenantName] & "/v2.0/.well-known/openid-configuration", queryString = [], response = Web.Contents(queryURL, [ Headers = [ #"Accept" = "application/json" ], ManualStatusHandling = {400} ]), body = Json.Document(response), result = if (Record.HasFields(body, {"error", "error_description"})) then error Error.Record(body[error], body[error_description], body) else body in result; //will be called as first function on login. StartLogin = (clientApplication, dataSourcePath, state, display) => try let codeVerifier = Text.NewGuid() & Text.NewGuid(), //needed for PKCE this will remove the need for a client secret. authorization_endpoint = openIDconfig(dataSourcePath)[authorization_endpoint], AuthorizeUrl = authorization_endpoint & "?" & Uri.BuildQueryString([ client_id = Settings(dataSourcePath)[client_id], redirect_uri = redirect_uri, response_type = "code", code_challenge_method = "plain", //needed for PKCE code_challenge = codeVerifier, //needed for PKCE scope=Settings(dataSourcePath)[client_id] & "/user_impersonation offline_access" //only if offline_access is requested a refresh_token is provided. ]) in [ LoginUri = AuthorizeUrl, CallbackUri = redirect_uri, WindowHeight = windowHeight, WindowWidth = windowWidth, Context = codeVerifier ] otherwise let message = Text.Format("Start Login Methode Error") in Diagnostics.Trace(TraceLevel.Error, message, () => error message, true) ; //after the user has done the MFA this function will be called FinishLogin = (clientApplication, dataSourcePath, context, callbackUri, state) => let parts = Uri.Parts(callbackUri)[Query], result = if (Record.HasFields(parts, {"error", "error_description"})) then error Error.Record(parts[error], parts[error_description], parts) else TokenMethod(dataSourcePath, "authorization_code", parts[code], context) in result ; //in the case of first login or token refresh this function will be called and request an (new) access token TokenMethod = (dataSourcePath, grant_type, tokenFieldValue, optional verifier) => let codeVerifier = if (verifier <> null) then [code_verifier = verifier] else [], codeParameter = if (grant_type = "authorization_code") then [ code = tokenFieldValue ] else [ refresh_token = tokenFieldValue ], queryString = codeVerifier & codeParameter & [ client_id = Settings(dataSourcePath)[client_id], grant_type = grant_type, redirect_uri = redirect_uri ], token_endpoint = openIDconfig(dataSourcePath)[token_endpoint], tokenResponse = Web.Contents(token_endpoint, [ Content = Text.ToBinary(Uri.BuildQueryString(queryString)), Headers = [ #"Content-type" = "application/x-www-form-urlencoded", #"Accept" = "application/json" ], ManualStatusHandling = {400} ]), body = Json.Document(tokenResponse), result = if (Record.HasFields(body, {"error", "error_description"})) then error Error.Record(body[error], body[error_description], body) else body in result; //Called on token experation Refresh = (clientApplication, dataSourcePath, oldCredential) => let refreshToken = oldCredential[refresh_token], result = TokenMethod(dataSourcePath, "refresh_token", refreshToken) in result; //Called on Logout Logout = (clientApplication, dataSourcePath, accessToken) => logout_uri;