forge.facebook.authorize returning incorrect Facebook token expiry time - facebook-ios-sdk

I've been using forge.facebook.authorize() successfully for several months in my app to get FB auth tokens.
However since a certain point last week I've been unable to validate any of the tokens it's been returning, due to an incorrect expiry time, and thus unable to sign-up or log-in any Facebook users since.
I must make clear that none of my code changed - this FB login was working fine previously and then suddenly stopped and hasn't since. A deployed live app suddenly stopped letting users log in with Facebook.
reponse from forge.facebook.authorize:
[FORGE] '"successfully authorized with FB",
{"access_token":"....","access_expires":1367922592459}'
I then turn the expiry seconds into a JS date object with this function:
function toDateTime(secs) {
var t = new Date();
t.setSeconds(secs * -1);
return t;
}
toDateTime(1367922592459);
> Sun Jun 11 -41335 12:22:41 GMT+0100 (BST)
See here the year is showing something crazy, definitely before Facebook was invented.
So anyway, then my code passes the FB auth data to Parse.com to log in a user, and Parse.com obviously throws it back for having an invalid expiry time.
The problem is occurring on iOS and Android apps built with trigger.io v1.4.29 and v1.4.33
Note: I have a working FB javascript login on my webpage (http://wewana.com/) which is connecting to the same Facebook app and the same Parse.com application. This page is not exhibiting any problems, so it seems the FB app is fine.

t.setSeconds(secs * -1);
Are you intentionally multiplying by -1? I'm not seeing why this could be expected to work.

var d = new Date(1367922592459);
There you go, simple and easy.
(Since JavaScript handles Date in milliseconds, there’s no need to do /1000 or something.)

tldr;
I was incorrectly processing the expiry timestamps as seconds. Processing them as milliseconds fixes the issue. Clearly there was an issue with toDateTime()
Full version:
For some time parse.com has been accepting invalid FB expiry dates. As users were getting logged in fine we did not know there was a problem.
Recently parse changed something so they error on invalid dates, causing our login to break.
Our function that translated seconds into a JS date object needed to be changed to convert based on milliseconds.
New working function:
function toDateTime(secs) {
var t = new Date(0);
t.setUTCSeconds(secs / 1000);
return t;
}

Related

Microsoft Graph returning null account even after passing a valid account ID

I am encountering a weird issue with Microsoft Graph on an integration that was built a few years back.
This issue started happening a few months back. After I sync a Microsoft Account and provide email and calendar read/write access, everything works fine for some time. I am able to retrieve emails and calendar events. However, after some time, I notice that when a call is made to GetAccountAsync with a valid AccountID, null is returned. This is causing AcquireTokenSilent to fail with the following error:
Error Code: user_null
Error Message: No account or login hint was passed to the AcquireTokenSilent call.
I have also noticed that this happens under the following scenarios:
When the WebJob (console app) is run every 15 minutes, I encounter this issue
To narrow down the root cause, I have deleted the WebJob to see if the issue occurs on the web app. It looks like the issue starts to occur after an hour or so even without the web job running.
I have upgraded to the latest version of MSAL and implemented 4.46.1.0 version of Microsoft.Identity.Client. I am using .NET Framework 4.8 and this is a .NET MVC 5 app.
Here's my code:
public async Task<string> GetAccessTokenAsync()
{
string accessToken;
UserExternalApp.Scope = string.IsNullOrWhiteSpace(UserExternalApp.Scope) ? "" : UserExternalApp.Scope;
// Load the app config from web.config
var microsoftScopes = UserExternalApp.Scope.Replace(' ', ',').SplitAndTrim(new char[] { ',' }).ToList();
var accountID = UserExternalApp.ExternalUserAccountID;
var app = ConfidentialClientApplicationBuilder.Create(ClientID)
.WithRedirectUri(DefaultRedirectUrl) // https:\//mywebsite.com
.WithClientSecret(Secret)
.Build();
app.AddDistributedTokenCache(services =>
{
services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Connection"].ConnectionString;
options.SchemaName = "dbo";
options.TableName = "TokenCache";
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
});
});
try
{
var account = await app.GetAccountAsync(accountID);
var query = app.AcquireTokenSilent(microsoftScopes, account); // This is where the error is thrown
var acquireTokenSilent = await query.ExecuteAsync();
accessToken = acquireTokenSilent.AccessToken;
}
catch
{
// This is the error thrown:
// Exception Type: MsalUiRequiredException
// Error code: user_null
// Exception Details: No account or login hint was passed to the AcquireTokenSilent call.
throw;
}
return accessToken;
}
I know the token is persisted on my SQL Server:
I think the MSAL uses an in memory token cache by default, Once the client logins, authentication information will be stored in cookie(if cookie has not been disabled). Even your web application restarts, the client will keep logged in.
To solve this, you can use custom Token cache serialization in MSAL.NET:https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-net-token-cache-serialization?tabs=aspnet.
Hope this helps.
I would like to share the resolution to this problem in case if it helps someone in the future. I feel that this is a Microsoft Bug that was introduced during one of their many upgrade process as this code went from working to broken without any change from our end. Here are the steps I took:
While exchanging the code for a token after user authentication, I retrieved and saved Account.HomeAccountId.Identifier, Account.HomeAccountId.ObjectId and TenantId for the account.
I implemented my own version of IAccount.
Instead of calling await app.GetAccountAsync(accountID), I used my implementation of IAccountand initialized it with the data I saved in Step 1.
I used this account to call app.AcquireTokenSilent(microsoftScopes, account).
And that's it! No error was thrown once this was done!

time in actions on google doesn't match my local time

I'm trying to develop google assistant using node.js firebase cloud functions and Dialogflow
so when I was trying console.log to test what it looks like when I deploy everything works just fine but when I test it on a simulator the time in the sentence doesn't match my local time ex. it says it's 11:00 AM while in my country it's 18:00. can anyone tell me how to fix it?
If you use Cloud Functions for Firebase, the webhook time is going to be set to the GMT timezone (+0). This is useful in some cases for consistency, with the downside that it won't guarantee to reflect the client.
In our recently published location sample we use a new timezone field in order to get the client timezone. With this, and the timezone in GMT, you can use a library like spacetime to get the time local to the user.
const spacetime = require('spacetime');
app.handle('location_granted', (conv) => {
const {id} = conv.request.device.timeZone;
let localTime = spacetime(new Date());
localTime = localTime.goto(id); // Set timezone
conv.add(`The Local Time is ${localTime.format('{time}')}.`)
});

Azure AD Easy Auth expires even when users are actively using application

We have a Single Page App (SPA) that uses Azure Active Directory "Easy Auth", e.g., the code-less solution. This seems to work ok when users first open the the application. They are redirected to the Microsoft login page and they can authenticate and then access the application.
Then, because its an SPA, users will navigate around and only fire Ajax requests. The problems come approximately 24 hours later when the session cookie expires. Users likely still have the same browser tab open and do not perform a full page refresh. Then they may be working on a record and at some point their next Ajax PUT request fails with a Redirect HTTP status and they loose their work.
So they key question is:
How can we make SPA Ajax requests extend a current user's session so that their session will not expire when they are actively using the application?
It seems like the Azure AD Easy Auth service does not "honor" activity on the part of the user, which leads us to believe that the session cookie never gets updated.
Note: We've recently done some testing with the /.auth/refresh endpoint and this does not solve the problem either.
There are several ways you can possibly solve this. Here are a few that I can think of:
Use local storage: The problem you mentioned is that user's lose their work due to the redirects. The problem of losing work can be solved if you persist the in-progress state in local storage so that it's available when they are redirected back to the page.
Switch to using tokens: The /.auth/refresh endpoint doesn't refresh the AppServiceAuthSession when using AAD because AAD doesn't support refreshing the user information. What you can do instead is authenticate with your backend using the x-zumo-auth tokens. The /.auth/refresh endpoint will correctly refresh these tokens. If you're explicitly logging in users using /.auth/login/aad, then you can add the session_mode=token as a query string parameter. This is done for you if you use the Mobile Apps JavaScript SDK. If login is automatic, then you'll need to add session_mode=token in the additionalLoginParams setting of your auth config. You can then parse the authentication token from the #token fragment which is added to the URL after the login completes.
Use hidden iframes: I haven't tried this myself, but if you can get it working it might require the least amount of code change. The idea is that you use a hidden iframe to re-login the user periodically when you detect they are active. The iframe would need to point to something like ./auth/login/aad?prompt=none&domain_hint={userdomain.com} where {userdomain.com} is the last part of the user's email address - e.g. contoso.com. These parameters get passed to the AAD login page, and the login should complete automatically without any user interaction. Test it manually a few times in a browser window to make sure it works correctly. The result should be an updated auth cookie with a fresh expiration.
Let me know in the comments if you have any questions or issues with any of these options.
Expanding on Chris Gillum's answer with implementation example:
Scenario: Single Page Application (SPA) with Progressive Web App (PWA) capabilities, hosted in Azure Web App. Added authentication using Azure Web Authentication/EasyAuth.
Ran into similar/same issue: Initial loads of the SPA worked fine, but after period of hour(s) (token expires) the app "breaks" - in SPA on iOS tablet that manifested for me with endless whitescreen and seemingly no practical fix (force killing did NOT resolve). Error messages thrown ranged from 401 (understandable) to service-worker refusing to process scripts/handle 302 redirects/etc (less obvious where problem may be).
SPA + Azure Web Authentication/EasyAuth tweaks:
If using MDM, disable "Block Safari navigation menu bar" feature in the MDM for this app. This appears to allow the app to work as expected after force kill (it would reload the page, see expired token, redirect to login and then back to the app). I'm not sure if this behavior is controllable in manifest.json, may be iOS specific capability.
Hidden iframe refreshing of token + Timer/check token periodically (in ajax calls, etc):
Note: As of ~2021-04, Chromium based browser worked with hidden iframe method. For other browsers the AAD page would experience errors and fail - current solution suggested would be storing app state -> navigate to AAD login page with redirect param -> User logs in and redirected back to the app -> App state restored w/ refreshed token.
refreshAuthToken() {
//Chrome based browsers work with silent iFrame based token reAuth
if (this.browserChromium()) {
let domainHint = "contoso.com"; //Domain of your organization users (e.g. me#contoso.com)
//Remove existing iframe (if exists), to minimize history/back button entries
let existingFrame = document.getElementById("authIFrame");
if (existingFrame) {
existingFrame.remove();
}
//Inject iFrame that will call endpoint to refresh token/cookie
console.log("Refreshing auth token (quietly)...");
let iframe = document.createElement("iframe");
iframe.id = "authIFrame";
iframe.style =
"width: 0; height: 0; border: 0; border: none; position: absolute; visibility: hidden;";
iframe.src = `/.auth/login/aad?prompt=none&domain_hint=${domainHint}`;
document.body.appendChild(iframe);
new Promise(r => setTimeout(r, 2000)).finally(() => resolve()); //Hacky method of "waiting" for iframe to finish
} else {
console.log("Refreshing auth token (via page reload)...");
window.location.replace("/.auth/login/aad?post_login_redirect_url=/?restoreData=true");
}
},
//
// Timer example:
//
setInterval(() => {this.refreshAuthToken()}, 1000 * 60 * 5); //Fire every 5 minutes
//
// And/or periodically call this function maintain token freshness
//
checkAuthToken() {
//this.authEnd = JWT from /.auth/me "exp" claim
let now = new Date() / 1000;
let expirationWindow = this.authEnd - 600; // Consider token expiring if 10 minutes or less remaining
if (now >= expirationWindow) {
console.log("Auth Token expired - Refreshing...")
this.refreshAuthToken();
} else {
// console.log("Auth token still healthy.");
}
}
Nicety: Enable anonymous access to PWA icons (if possible). iOS requires icons be publicly accessible when saving PWA to homescreen, otherwise uses screenshot of app rather than formal icon: https://stackoverflow.com/a/67116374/7650275

UWP - how can I test a closing of app when trial are expired with CurrentAppSimulator?

I have a problem with trial period in my app and I tried to reproduce it in the test mode.
I want to test the following behaviour:
App has trial period of 1 day.
The user opens the app a day later, when trial is expired.
According to this documentation the message dialog will be shown.
When I tried testing this scenario, I found the file called %userprofile%\AppData\local\packages\<package name>\LocalState\Microsoft\Windows Store\ApiData\WindowsStoreProxy.xml.
This file contains:
<LicenseInformation>
<App>
<IsActive>false</IsActive>
<IsTrial>false</IsTrial>
</App>
........
</LicenseInformation>
In this case my app is open. But look at the behaviour when I check a license using this code:
LicenseInformation^ licenseInfo = CurrentAppSimulator::LicenseInformation;
if( !licenseInfo->IsActive )
{
//I get into the code section
}
But my app still opens successfully.
Will my app be closed by the OS in this case, when I publish it in Store and load with Store (with Windows::ApplicationModel::Store::CurrentApp and according to the documentation)?
How can I simulate this closing (by the OS)?

Incontext Paypal Integration- without moving away from site [Nodejs, Expressjs]

I am using nodejs and expressjs and using "paypal-ec" node package to do incontext paypal integration.
This is invoked with the following piece of code:
<script src='https://www.paypalobjects.com/js/external/dg.js' type='text/javascript'></script>
<script>
var dg = new PAYPAL.apps.DGFlow(
{
trigger: 'paypal_submit',
expType: 'instant'
});
</script>
What I have achieved with this
I am able to make payment in the Paypal Sandbox environment but it shows me older payment screen where in user needs to fill in the details of address etc (I am not able to attach screenshot because of credits)
What I want to achieve
I am trying to make payment with the screens where in user doesn't need to prefill any data also it gives better UI.
Some experience like provided in this plukr link http://plnkr.co/edit/3vfNSVRyq86pDR5mH4HH?p=preview
The problem with the given piece of code in plunk is that it doesn't expose what is there in the action method and how can I provide amount to it (or any other details if any).
Any kind of help is appreciated.
I get it but don't claim to be a node dev - yet :) so this is "conceptual":
At the end of the day, the server-side call (SetExpressCheckout) where you send your trnx details (items, price, return/cancel urls, etc.) to Paypal and obtain a token is unchanged (with the documented limitations and ignored params that is).
The change is in the front-end where:
the in-context js script<script async src="//www.paypalobjects.com/api/checkout.js"></script>
new redirect url : https://www.paypal.com/checkoutnow?token=[the token you obtained]
are in play
The linked sample's server-side SetExpressCheckout process is the:
http://166.78.8.98/cgi-bin/aries.cgi?sandbox=1&direct=1&returnurl=http://166.78.8.98/cgi-bin/return.htm&cancelurl=http://166.78.8.98/cgi-bin/cancel.htm
You can see the returnurl and cancelurl set (but it could have been done server side as well). This will obtain the token which is needed for the subsequent steps.
If you can inspect the traffic, you'll see the response where the redirect (that is "caught" in the front end and displayed "in-context"):
HTTP/1.1 302 Found
Date: Sun, 05 Jul 2015 16:00:48 GMT
Server: Apache/2.4.7 (Ubuntu)
Access-Control-Allow-Origin: *
Location: https://www.sandbox.paypal.com/checkoutnow?useraction=commit&token=EC-94X58918K2362702E&ul=0
This sample is probably more detailed and "less magical" (shows more of what's going on) and is what helped me implement:
http://plnkr.co/edit/UhNka4VaaRRGY1TK32LE?p=preview
Hth.

Resources