Issue with IAccessTokenProvider on iOS devices - ios

In my Blazor WebAssembly, I call the API to fetch some data. The user must be authenticated against the Identity Server. In the header of the request, I add the user token.
In the page, the user has to select some dropdown list and then press the submit button. On the click, the application calls the API with the following function (the result conversion is omitted).
private readonly IAccessTokenProvider _accessToken;
public APIService(HttpClient httpClient, IAccessTokenProvider accessToken)
{
_httpClient = httpClient;
_accessToken = accessToken;
}
public async Task<APIResponse> GetAttributeAsync(APIRequest apirequest)
{
var tokenResult = await _accessToken.RequestAccessToken();
tokenResult.TryGetToken(out var token);
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Post, $"typing");
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", token.Value);
// ...
return APIResponse;
}
Then, it hides the form and display the result from the API. There is a back button, that hides the result and shows again the form.
The web application is working fine as I expected. Then, I wanted to try the web application on iOS (iPhone or iPad). I fill the form, submit the request and display the data as expected. Then I click the button to display again the form and send another request: when I sent the request, the function above generates an error
Arg_NullReferenceException
If I refresh the page, the web application is working again. The behaviour is consistent in iOS with Safari, Edge and Chrome.
I guess, for some reasons, the RequestAccessToken() is not able the second time to retrieve the user token.
Have someone else had this issue? How can I fix it?
Update
After a day of testing, what I can say is that on Windows the function RequestAccessToken() finds the token every time I ask to retrieve it. On iOS devices, the application can read the token only once and then the token is removed. I don't know if it is correct, but I added a small cache in the Local Storage to save the token and reuse it.
Is it the correct way to approach the problem?

Related

How to handle user creation with Firebase social auth and a Rails backend

I am building a react native application with a rails backend but I decided to go for Firebase for authentication only since it's quite a bit cheaper than Auth0 or Okta.
The setup went fine but I am having trouble figuring out where to call my own backend to create/update a user in the sign in process.
This is my current sign in function that is triggered when pressing the 'Sign in with Facebook' button:
async function handleAuthentication() {
const result = await LoginManager.logInWithPermissions(['public_profile', 'email']);
if (result.isCancelled) {
return;
}
const data = await AccessToken.getCurrentAccessToken();
if (!data) {
return Alert.alert('Error', 'Something went wrong while authenticating with Facebook.');
}
const facebookCredential = auth.FacebookAuthProvider.credential(data.accessToken);
// This token doesnt seem to work
await upsertUser(facebookCredential.token);
await auth().signInWithCredential(facebookCredential);
}
What I get back from the auth.FacebookAuthProvider.credential(data.accessToken) is an object with a token but that doesn't seem to be useful.
When I call auth().signInWithCredential(facebookCredential); I do get back the user data that I need to create a user BUT that already triggers the authentication system and sets the user as signed while he/she isn't created in the backend yet.
So ideally, I would like to create the user before calling signInWithCredential.
Although I'm not sure how to do this or with what token?
I use a Rails backend where I can also decode the token if necessary.
Any help would be greatly appreciated!

"Internal" Error When Submitting Form With Firebase onCall Function on IOS Safari

I am trying to submit a form but I get an "internal" error after submit on IOS Safari. Happened on two separate devices. I'm using Firebase functions onCall function. Client code:
var contactForm =
window.firebase.functions().httpsCallable('contactForm');
let result = await contactForm({ accountUID, foldersFilter,
firstName, lastName, email, cellNumber, dobDay, dobMonth })
And server code:
exports.contactForm = functions.https.onCall(( data, context ) => {
return contactForm.contactForm( data, context )
});
This function is called via form. The form works great on chrome, safari desktop, but for some reason it gets an internal errror sometimes when testing on IOS device. At first I thought it only happened when I was using autofill, but I've tested more and I get the same error when not using autofill too.
The confusing thing is my function code is actually never being called (I don't see any firebase function logs). Here is my console in safari:
The network connection was lost.
Fetch API cannot load https://us-central1-projectId.cloudfunctions.net/contactForm due to access control checks
Failed to load resource: The network connection was lost.
internal
Why won't this form submit on ios safari?
I fixed the issue. Turns out it has something to do with Google Cloud Functions being IPv4, and Safari requiring IPv6. I suspect this will become a bigger issue moving forward. I'm having to move all onCall Firebase functions to https triggers. In order to make https triggers work, you have to use a custom domain in Firebase hosting and rewrite to your function endpoint.
{
"hosting": {
...
"rewrites": [
{
"source": "/api/contactForm",
"function": "contactForm"
}
}
and so now instead of calling https://us-central1-projectId.cloudfunctions.net/contactForm to trigger my api. I call https://customdomain.com/api/contactForm

Google OAuth 2 access token not working as expected some hours after authorizing

I'm using the Google Identity Platform's OAuth 2.0 flow to authorize a javascript/HTML teacher observation form to write to a Google Sheets document. Everything is working well most of the time; however, last night one of our principals hit the following error:
"Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project."
I determined that he had launched the observation tool in the afternoon, and now maybe five hours later was trying to click the submit button. My hunch was that the token had expired, but from Google's documentation it seems like the JS auth library is meant to handle refreshing the access token as necessary - I believe it's not actually possible to get a refresh token to do anything manually.
I'm using what is essentially the sample auth code, and the app responds to being signed out appropriately. That is, if I sign out in another tab, the submit button is disabled and the sign-in button appears again. Assuming token expiration is the issue here, any ideas on the correct way to identify if the token has expired and how to request a new one, ideally without user interaction? Or if it's not an expiration issue, what else could it be? This user has successfully submitted data in earlier observations; it was just this one time when he waited ~5 hours (potentially losing internet connectivity / sleeping his laptop) during that time.
Here's the auth code:
var clientId = ""; //id removed
var discoveryDocs = ["https://sheets.googleapis.com/$discovery/rest?version=v4"];
var scopes = "https://www.googleapis.com/auth/spreadsheets";
var authorizeButton = document.getElementById('authorize-button');
function handleClientLoad() {
gapi.load('client:auth2', initClient);
}
function initClient() {
gapi.client.init({
discoveryDocs: discoveryDocs,
clientId: clientId,
scope: scopes
}).then(function () {
gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus);
updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
authorizeButton.onclick = handleAuthClick;
});
}
function updateSigninStatus(isSignedIn) {
if (isSignedIn) {
authorizeButton.style.display = 'none';
document.getElementById('submit').disabled = false;
findRow(); //find the empty row once we're logged in
} else {
authorizeButton.style.display = 'block';
document.getElementById('submit').disabled = true;
}
}
function handleAuthClick(event) {
gapi.auth2.getAuthInstance().signIn();
}
Thank you!
Similar issues that i had resulted in issues from that Authorized Javascript origins.
"In the Authorized JavaScript origins field, enter the origin for your app. You can enter multiple origins to allow for your app to run on different protocols, domains, or subdomains. You cannot use wildcards. In the example below, the second URL could be a production URL." taken from https://developers.google.com/identity/sign-in/web/devconsole-project.If prompt to view task came from an email, the email origin must be verified -or- the device is used for multiple accounts, the token will not stay. If the api is being improperly used, it will allow functionality for a short period of time , then fail.
This may be useful, in the authflow, you do not have scope or id in options
/** * Initiate auth flow in response to user clicking authorize button. * *
#param {Event} event Button click event. */ function
handleAuthClick(event) {
gapi.auth.authorize( {client_id: '[#app:client_id]', scope:
["googleapis.com/auth/calendar"], immediate: false}, handleAuthResult);
return false; }
I believe How to refresh expired google sign-in logins? had the answer I needed. Since all of my API calls happen at once, I added a new Date() when the page loads, a second new Date() when the submission flow begins, and if they are more than 45min (2,700,700ms) apart, I use gapi.auth2.getAuthInstance().currentUser.get().reloadAuthResponse() to force an access token refresh, as documented at https://developers.google.com/identity/sign-in/web/reference#googleuserreloadauthresponse.
Hopefully Google will eventually update their documentation to reflect this now-necessary step when using the auth2 flow vs the older auth flow.
Time will tell if this actually solved the issue, but I'm hopeful!
I hope it helps you friend that error is because you have the wrong time, you go to date and time settings then press synchronize now.

How to detect that the current request is an authentication callback?

I have a single-page JavaScript application and I'm using the Auth0 service for signup/login.
I have integrated the Lock widget and I'm saving a string to localStorage after a user is authenticated, like so:
lock.on("authenticated", function(authResult)
{
localStorage.setItem('login', authResult.idToken);
}
The problem is that when Auth0 redirects them back to my application after logging in, the authenticated event is fired only after page loaded, but by that time, I've already done the check to see if the localStorage string is set (which it is not); therefore, the user just keeps getting asked to login again:
if(localStorage.getItem('login') == undefined)
{
lock.show(function(err, profile, token)
{
// ...
}
}
I tried to see if there was anything special passed in to the page after a callback - but the referrer isn't always there.
If I don't automatically prompt the user to login, but instead show a login button - the authenticated event never fires for some reason.
How do I get around this?
Based on the information provided you seem to be using Lock in redirect mode and if that's the case you can use the hash_parsed event as a way to know if Lock found a response that it will process.
Every time a new Auth0Lock object is initialized in redirect mode (the default), it will attempt to parse the hash part of the URL, looking for the result of a login attempt. After that, this event will be emitted with null if it couldn't find anything in the hash. It will be emitted with the same argument as the authenticated event after a successful login or with the same argument as authorization_error if something went wrong.
Leveraging this event you could do the following:
Subscribe to the hash_parsed event:
If hash_parsed is emitted with null and localStorage has no indication the user already logged in then redirect to login.
If hash_parsed is emitted with a non-null value that either the authenticated or authorization_error will be emitted and you can react accordingly.
Some sample code:
lock.on("hash_parsed", function (response) {
if (!response && !localStorage.getItem('login')) {
// Redirect to the login screen
} else {
// Either the user is already logged in or an authentication
// response will be processed by Lock so don't trigger
// an automatic redirect to login screen
}
});

How to authenticate user with token (stay authenticated in iPhone)

I have two related questions and I hope someone help me because I've been stuck for 2 days
First: mobile phone failed to authenticate
Here is what I have done:
1- user signs up
2- token released
3- token saved in user's device
but then when the same user try to do API requests I get
Rooute to sign up :
$api = app('Dingo\Api\Routing\Router');
$api->version('v1', function ($api) {
$api->post('auth/signup', 'App\Api\V1\Controllers\AuthController#signup');
then I get a token , so I guess everything looks great!
then now when the same device sends a post request to laravel I get this message
"message": "Failed to authenticate because of bad credentials or an invalid authorization header."
this is the route to the post request
$api->group(['middleware'=>'api.auth'],
function ($api) {
$api->post('auth/ios', 'App\Api\V1\Controllers\AuthController#create');
Second: is my method right to save data made by a mobile phone?
Since I couldn't test this method I'd like to know if this is at least one of the right ways to receive data and save it. The reason to save it is because I will show it in a control panel.
public function create(Request $request)
{
$user = new User();
$id = Auth::id();
$user->phone = $request->input('phone');
$user->city = $request->input('city');
$user->street = $request->input('street');
$user->save();
return 'Employee record successfully created with id ' . $user->id;
}
I understand that you are authenticate users based on api token.
Here is what you could do :
set up a column called api_token in users table by adding the following migration
$table->string('api_token', 60)->unique();.This generates a random api token for every user.
send the api_token back to the user's device and save it there
Send it back with every request. Preferalbly set it up globally and send it in the request Authentication request header
Get the authenticated user like so$user= Auth::guard('api')->user();
Laravel takes care of all the authentication stuff behind the scenes.
Learn More about this here

Resources