I'm using the PowerBi-Javascript library to embed a powerBI dashboard:
let embedConfig = {
type: 'dashboard',
tokenType: window.powerbi-client.models.TokenType.Aad,
id: '...',
embedUrl: '...',
accessToken: '...'
};
let embedInstance = powerbi.embed(elem, embedConfig);
Later I want to update the access token to ensure the dashboard can keep updating its data. We use the following approach:
embedInstance.setAccessToken('...')
.then(function (r) {
console.log('New access token set succesfully');
})
.catch(function (e) {
console.error('Error setting access token', e);
});
The setAccessToken promise resolves (I see the success log), however, I'm not certain that the access token is correctly propagated to the PowerBI instance.
What I find is that the next time the data on the embedded dashboard updates, I get a 401 response logged in the console for a request to https://wabi-uk-south-redirect.analysis.windows.net/powerbi/metadata/dashboards/[my-id].
If I subsequently refresh the data (before the access token expires again), everything seems to work fine.
It seems that the first data refresh after a new access token is set over an expired one fails.
This is a big problem for me because the data we have refreshes (automatically) less frequently than the access token expires, so after the first access token expires, the data never refreshes in my embedded instance.
Has anyone else had this problem, or does anyone know of a way around this?
UPDATE:
The requests that fail are always to /powerbi/metadata/dashboards/[my-id], whereas requests that work and cause the data to refresh are to /powerbi/metadata/dashboards/[my-id]/tiles. Note that I am not triggering these refreshes programatically, just by going to the powerbi web app (app.powerbi.com) and clicking "Refresh dashboard tiles".
a call to /powerbi/metadata/dashboards/[my-id] happens only on dashboard load to get the dashboard metadata.
It is NOT the best practice to embed a dashboard with a near expiry token.
In order to embed a dashboad make sure the AAD token you have does have few minutes until expiry.
Calls to sync service which update the tiles size and position and tile data are working OK.
In addition, in your application, set a timer which updates the token few minutes before expiry, this will make your dashboard more "live", because if the sync service fails with un-authorized exceptions few times, it goes into a sleep for almost 30 seconds.
please let me know if that helps.
Related
I am writing a web-based app that uses AWS Cognito as the authentication service. I use 'aws-amplify' to implement the client-app.
I am using Auth.updateUserAttributes() to update a custom attribute of users on Cognito. However, I found that the call of this function would clear all the Cognito-related items, including idToken, refreshToken, and accessToken stored in localStorage. As a result, the web app behaves like sign-out.
Here is the code about Auth's configuration
Amplify.configure({
Auth: {
userPoolId: process.env.REACT_APP_AWS_COGNITO_USER_POOL_ID,
region: process.env.REACT_APP_AWS_COGNITO_REGION,
userPoolWebClientId: process.env.REACT_APP_AWS_COGNITO_APP_CLIENT_ID,
storage: window.localStorage,
authenticationFlowType: 'CUSTOM_AUTH',
},
});
and the code I wrote to update the user's attribute. (I followed the example code from the amplify docs https://docs.amplify.aws/lib/auth/manageusers/q/platform/js/#managing-user-attributes)
let user = await Auth.currentAuthenticatedUser();
console.log(JSON.stringify(localStorage)); // it outputs the localstorage with idToken,
// refreshToken, accessToken and other items
// start with 'CognitoIdentityServiceProvider'
const result = await Auth.updateUserAttributes(user, {
'custom:attributes_1': '123456789',
});
console.log(result); // OUTPUT: SUCCESS
console.log(JSON.stringify(localStorage)); // Only 'amplify-signin-with-hostedUI'.
// idToken, refreshToken, accessToken and
// other items were gone. No key, no value.
After the last line, I could not interact with the web page anymore. If I refreshed the web page, I found I had signed out and had to sign in again.
It was still the same if I changed the storage for Auth from localStorage to sessionStorage.
Here are my questions:
Is this kind of behavior normal? Does Auth.updateUserAttributes() leads to a force sign-out?
If it's true, is there any way to avoid a mandatory sign-out?
If it's not, what's wrong with my code or configuration? Or should I do some specific configuration for the Cognito service?
Thanks a lot!
Well, I figured it out after reading the source code of aws-amplify.
Behind the call of Auth.userUpdateAttributes, amplify will finally call CognitoUser.refreshSession(refreshToken, callback, clientMetadata) (https://github.com/aws-amplify/amplify-js/blob/f28918b1ca1111f98c231c8ed6bccace9ad9e607/packages/amazon-cognito-identity-js/src/CognitoUser.js#L1446). Inside this function, amplify sends an 'InitiateAuth' request to Coginito. If an error of 'NotAuthorizedException' happens, amplify calls clearCachedUser() that delete everything I mentioned in my question from the localStorage.
There was an error of 'NotAuthorizedException' happening and reported by the network work monitor of Chrome Browser'. I thought it was generated after the sign-out-like behavior. However, it turned out to be triggered because no deviceKey was passed to the request's parameters.
So the whole story was:
I set remember device options in Cognito;
I used 'CUSTOM_AUTH' as the authentication flow type;
When a user successfully signed in to my application, Cognito didn't give the client the deviceKey because of the 'CUSTOM_AUTH' authentication flow type.
When Auth.userUpdateAttributes() was called, CognitoUser.refreshSession() was called behind it. It attached no deviceKey to Cognito when it sent ana request to asked Cognito to refresh the token. The Cognito rejected the request with an error of 'NotAuthorizedException'. The CognitoUser.refreshSession() handled the error and called clearCachedUser() to delete the stored tokens and other info from the localStorage.
My final solution is to turn off the remember device option since I have to use 'CUSTOM_AUTH' as the authentication flow type according to my application's functional requirements.
According to https://aws.amazon.com/premiumsupport/knowledge-center/cognito-user-pool-remembered-devices/, the remember device function only works when the authentication flow type is set as 'USER_SRP_AUTH'.
I developed my Django based webapp with token authentication by following this tutorial of Brad Traversy (https://www.youtube.com/watch?v=0d7cIfiydAc) using Knox for authentication and React/Redux for the frontend. Login/logout works fine (here is Brad's code: https://github.com/bradtraversy/lead_manager_react_django/blob/master/leadmanager/frontend/src/actions/auth.js --> logout using a POST request), besides one issue: When the user stays away from the computer for a long time, the token expires in the meanwhile. So when the user returns he is still in the logged in zone of the website, but as soon as he opens a React component with data loading from the DB a 401 error is thrown in the console ("Failed to load resource: the server responded with a status of 401 (Unauthorized)"). Then the user has to go on "logout" and login again.
This is not optimal, I would prefer that after the user returns, the system realizes the token expiry and logs the user automatically out. I have thought of the following approaches, but I am not sure how to implement it or which one is best:
1) For every API request: if the answer is 401 --> logout (this might also log the user out in case the token has not expired, but if there is some other permission problem) - seems not optimal to me.
2) Instead one could also create a testing route e.g. api/auth/check with a Django view including the typical check
permission_classes = [permissions.IsAuthenticated]
and if 401 returned --> logout. So that would mean for every database request I have another rather unspecific database request before.
3) Check at every API request specifically if the token has expired --> how to do it? In the docs (https://james1345.github.io/django-rest-knox/) I couldn't find a method to check token validity. I see in the database table "knox_authtoken" an expiry date and a huge code in the column "digest", but this is obviously encrypted data and cannot be compared with the token value that one has in the browser under local storage.
I would be glad to receive recommendations on how to best implement this!
This can be done in multiple ways.
I dont see the reason kicking a user out automatically, but if you want to do that you can either:
Create an URL which will be only for checking if the authentication is valid every 5 secs or so
Use web sockets to send a realtime message once the token has expired.
Put the logic in the frontend, for example store how long the token is valid, and run a timeout, after the timeout is finished relocate him to login.
Jazzy's answer - option 3 - brought me on the right way (thank you!), but working with timers on the frontend side, was initially not successful, since starting a timer within a React component would only run as long as this component is visible. I have no component that is visible all the time of the user session. I changed the expiry duration of the token within Django settings from default value of 8 hours to 72 hours and implemented an idle check on the frontend with this package: https://www.npmjs.com/package/react-idle-timer . So as soon as my application is not used for 2 hours I call the logout action (api/auth/logout). With this approach I don't need to care about the expiry time of the token on Django side, since no user will be active throughout 72 hours. As soon as he logs in again, he will receive a new token.
New solution:
I decided to not bother users too often with logging in and found this nice strategy:
we choose to never expire Knox tokens
we set expiry date for Django session to 90 days from last login
if user does not log in for > 90 days, he will make at some point a request to the backend (e.g. data requests), there we include a check if the session data is available
if 'some_session_variable' in request.session:
# whatever logic you need
else:
return HttpResponse("logout")
Since session variable will not be available after the expiry the 'logout' string is returned. On the frontend we check every response for 'logout' string. If it is being returned we initiate the logout process. The idle timer is not used anymore (as it is not so reliable in my experience).
I'm working on an ASP.NET MVC application that is using PowerBI Embedded to display some reports. I started with the application from the tutorial and have not made any changes to the code rendering the report. I did, however, update all the nuget packages to the latest versions.
In general, the app works fine and reports are being rendered correctly. However, sometimes I run into a problem where all the reports stop loading and I only get a flashing Power BI logo.
If I open a new browser session in incognito mode and log in on the same user it all works fine again. However, in the original tab it doesn't work until I close everything or reset the cache.
I noticed that it always happens after I am logged in for a longer period of time (30-60 minutes maybe). My best guess was that there's something wrong with the tokens, but I am not able to track it down.
Any hints what might be causing it?
UPDATE:
I just noticed that after longer period of time the logo stops flashing and I get an error "This content isn't available". When I look into the browser console I get this:
wabi-west-europe-b-primary-redirect.analysis.windows.net/explore/reports/fcbf92f1-f8d7-4c61-aeb3-06f195835413/modelsAndExploration?preferReadOnlySession=true:1 Failed to load resource: net::ERR_SPDY_PROTOCOL_ERROR
reportEmbed.min.js:1 ERROR Error: Uncaught (in promise): Object: {"message":"LoadReportFailed","detailedMessage":"Get report failed","level":6,"technicalDetails":{"requestId":"6d99f480-0f1c-47d0-9598-cab569018dd0"}}
at A (reportEmbed.min.js:1)
at A (reportEmbed.min.js:1)
at reportEmbed.min.js:1
at e.invokeTask (reportEmbed.min.js:1)
at Object.onInvokeTask (reportEmbed.min.js:1)
at e.invokeTask (reportEmbed.min.js:1)
at t.runTask (reportEmbed.min.js:1)
at g (reportEmbed.min.js:1)
at t.invokeTask (reportEmbed.min.js:1)
at i.useG.invoke (reportEmbed.min.js:1)
Ye # reportEmbed.min.js:1
This happens because the embedToken you use for retrive the dashboard from the powerBi rest api expires after approx 1 hour.
You have to refresh the token with a new one before the token expires, and this is not automatically handled from the Microsoft api (as far as i know).
You have to use the authentication token previously obtained and requets a new embedToken.
Consider that also the authentication token expires after some time, if this happens you have to obtain a new one before the request of the new embed token.
You can do this re-logging with user/pass or, a better solution, using the refresh token that the api gives you when you authenticate (when autenticated you retrive an auth token and a refresh token you can use for request a fresh auth token without logging again with user-pass).
Some azure documentation here about the authentication for use an azure resource.
Update:
Consider also that in the object embedToken you obtain there is the expiration date you can use for refresh the token before it expires, here some docs from azure
The problem was, as EttoreP correctly pointed, casued by an expired token. However, the main culprit was caching. My App is not a Single Page Application, so the token should be acquired with every page load. It appeared that the results were cached for longer than the expiration time of the token. After running the page again after an hour it was using the cached token and causing errors. Adding an OutputCache attribute to the controller solved the problem. Thanks for the help and good hints!
[OutputCache(NoStore = true, Duration = 0)]
This happens because your of your implementation, a good ETL can be resolve your problem, but check your connection and the cache timeout implementation.
We have to give our users the option to sign out of our application. This would require them to log back in for further use. However, it appears the auth0 session cookie is not deleted for some reason when implementing the https://YOUR_AUTH0_DOMAIN/v2/logout?returnTo=http%3A%2F%2Fwww.example.com.
Even though the redirect works, the user is automatically logged back in after calling webAuth.authorize(); while you would expect to be asked to re-enter your credentials.
Calling this function for the first time, the user is required to enter username and password. However, they are never required again until the token expires.
Unfortunately, even the examples provided (via download section) do not address this. Wondering if this is even possible but it seems like the Auth0 website itself handles it correctly.
Here is the code example:
var logoutBtn = document.getElementById('vwLogoutBtn');
logoutBtn.addEventListener('click', logout);
function setSession(authResult) {
// Set the time that the access token will expire at
var expiresAt = JSON.stringify(
authResult.expiresIn * 1000 + new Date().getTime()
);
localStorage.setItem('access_token', authResult.accessToken);
localStorage.setItem('id_token', authResult.idToken);
localStorage.setItem('expires_at', expiresAt);
}
function logout() {
// Remove tokens and expiry time from localStorage
localStorage.removeItem('access_token');
localStorage.removeItem('id_token');
localStorage.removeItem('expires_at');
webAuth.logout({
returnTo: 'http://staging.myproject.com/prototype/home.html',
client_id: AUTH0_CLIENT_ID
});
displayButtons();
}
function displayButtons() {
if (isAuthenticated()) {
getProfile();
} else {
//You are not logged in
webAuth.authorize();
}
}
handleAuthentication();
});
We have also tried using: https://YOUR_AUTH0_DOMAIN/v2/logout?returnTo=http%3A%2F%2Fwww.example.com
However, every time the user logs out and it hits the login page, the user is automatically logged back in.
Any help/guidance is greatly appreciated. Thank you,
After a lot of tests I am actually able to answer my own question LOL
Problem was that the logout (session cookie delete) in combination with the re-login happened too fast. Putting a delay on calling webAuth.authorize() showed that the user is successfully logged out.
You certainly don't want to put a delay on this function. In my case I am now forwarding to a "Logged out" page that also offers the option to log back in.
We are integrating on our application the Office 365 functionality throught MSGraph rest api and we are currently getting trouble with the validation of Refresh Tokens, this is the response error code from the server on a invalid petition:
"error":"invalid_grant","error_description":"AADSTS70002: Error
validating credentials. AADSTS70008: The refresh token has expired
due to inactivity.??The token was issued on
2016-04-27T11:44:49.4826901Z and was inactive for 14.00:00:00.
This is annoying because we need the users to aquire their credentials again logging in on Microsoft servers.
Is there any option to avoid Refresh token being invalidated due to inactivity? Or to make longer this expiration?
Refresh tokens have a finite lifetime. If a new token (and refresh token) isn't requested before that time they will expire. Once this happens the user must re-authenticate.
If you need to have perpetual access to the account, you will need to manually refresh the token periodically. You may want to look at this article. It covers the basics of how v2 Endpoint works (and the various token lifetimes).
In most of my implementations I use a queue to handling refreshing tokens. I queue each token to be refreshed at 10 days. If it fails I resubmit to the queue. If it is still failing at day 12 I email the user to inform them there was an issue and they will need to re-authenticate.
UPDATE
Refresh token lifetime was recently changed to until-revoked. You can read about the change here
This is general OAuth (not AAD-specific): obtaining an access token is a 2-step process. The first step is to obtain an auth code which requires the user to authenticate. The second step is to redeem an access token and a refresh token from the auth code. This second step is purely programmatic, i.e. the user need not be present. The app can keep repeating the second step, i.e. redeeming a new access token and a new refresh token from the latest refresh token without the user even know about it.
Your app should schedule frequent 'refreshes' of the refresh token. You can do this at any time while the app is running.
If the user doesn't use the app for an extended period of time, like about 2 weeks (I believe), the refresh token would naturally expire. If you want to avoid that, you'll have to schedule a dedicated job to refresh the token.
Zlatko