Ionic 2 - Storage always empty until reloading the app - ionic-framework

I am currently building an application with Ionic 2 and using the Storage plugin to hold my values which are pretty much just an API Token and user profile since the application pulls all data from an API.
I am testing the application via ionic serve because no native functions are used but now I am facing the problem that every time I store a value in the Storage the value is not accessible until I reload the app which is kind of annoying because after the user logs in he gets redirected to a page that requires the API token which is not available until I reload the app so the whole thing gets stuck in a loop.
Ionic Storage is using IndexedDB in the browser where I can see that the values have been stored when I check them with Chrome Developer tools.
I have been trying to figure out the issue but can't find any reason why the storage values are not available until reloading the app.
import { Injectable } from '#angular/core';
import { Storage } from '#ionic/storage';
import { HttpClientService } from './http-client-service';
import 'rxjs/add/operator/map';
#Injectable()
export class AuthService {
constructor(public events: Events, public storage: Storage, public http: HttpClientService) {
//
}
login(user) {
var response = this.http.post('login', {
email: user.email,
password: user.password,
});
response.subscribe(data => {
this.storage.set('api_token', data.token);
console.log('raw : ' + data.token); // shows the api token
this.storage.get('api_token').then((value) => {
console.log('storage : '+ value); // is empty...
});
});
return response;
};
}
Edit: I managed to track down the issue to the storage running async which results in the token not being added to the headers.
createAuthorizationHeader(headers: Headers) {
// this does add the header in time
localStorage.setItem('api_token', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vYXBpLndpaHplLmRldi9sb2dpbiIsImlhdCI6MTQ4MTE4MzQyOCwiZXhwIjoxNDgxMTg3MDI4LCJuYmYiOjE0ODExODM0MjgsImp0aSI6IjdlNTE1WUEwWmE4NWc2QjUiLCJzdWIiOiIxIiwidXNlciI6eyJpZCI6MX19.T4KpqgCB8xU79vKyeLG4CJ0OHLpVI0j37JKIBJ_0CC4');
headers.append('Authorization', 'Bearer ' + localStorage.getItem('api_token'));
// this does not add the header in time
return this.storage.get('api_token').then((value) => {
headers.append('Authorization', 'Bearer ' + value);
});
}
getHeaders(path) {
let headers = new Headers();
headers.set('Accept', 'application/json');
headers.set('Content-Type', 'application/json');
if(!this.isGuestRoute(path)) {
this.createAuthorizationHeader(headers);
}
return new RequestOptions({ headers: headers });
}
get(path: string) {
return this._http.get(this.actionUrl + path, this.getHeaders(path))
.map(res => res.json())
.catch(this.handleError);
}

Alright, looked in the ionic docs and I do understand why you put them both underneath eachother since they also display it like that in the docs.
But Storage.set(key, value) :
Returns:
Promise that resolves when the value is set
This means that you cannot use it the way you are using it (hence why they added a comment with //or ....
Since resolving a Promise is asynchronous.
If you want to use the value like you're currently using it (which seems a bit odd but probably for you to test if the value is set correctly) you should use
this.storage.set('api_token', data.token).then(() => {
this.storage.get('api_token').then((value) => {
console.log('storage : '+ value); // is empty...
});
});
console.log('raw : ' + data.token); // shows the api token
If you would like some more information about why this happens, check out this SO answer (I prefer second one) Asynchronous vs synchronous execution, what does it really mean?

Related

Fetch Post request not working in Custom functions Office Addin [TypeError: Network request failed]

I having been facing this error in custom functions excel Add-in, where I'm trying to call an external service inside a custom function. It works fine for a GET request such as this:
function stockPrice(ticker) {
var url = "https://api.iextrading.com/1.0/stock/" + ticker + "/price";
return fetch(url)
.then(function(response) {
return response.text();
})
.then(function(text) {
return parseFloat(text);
});
}
CustomFunctionMappings.STOCKPRICE = stockPrice;
Taken from https://learn.microsoft.com/en-us/office/dev/add-ins/excel/excel-tutorial-custom-functions#create-a-custom-function-that-requests-data-from-the-web
But gives an exception for a POST request like this:
function stockPrice(ticker) {
var url = "https://westcentralus.api.cognitive.microsoft.com/text/analytics/v2.0/sentiment";
return fetch(url, {
method: 'POST',
headers: {
'Ocp-Apim-Subscription-Key': key,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(body))
.then(function(response) {
return response.json();
})
.then(function(response) {
return response.somevalue;
})
.catch(e => {
console.error("Caught exception");
return JSON.stringify(e);
});
}
The above is just a sample to have an idea, of how I'm calling my service. I have tried it with 2-3 different services, and I figured out that after running fetch, the code goes to catch block, and the error value that is returned in the excel is an empty object '{}'. Since there are no ways to debug custom functions on windows, and since there is no specific error description, I'm unable to figure out the issue. I have also added my service domain to App Domain list in manifest file but still no effect.
I am not sure that particular API accepts POST requests, so you maybe running into that.
Debugging in Windows is still being worked on but you can use Excel online and F12tools to debug.
If you are on Windows, you can console.log statements in conjunction with the Runtime logging:
https://learn.microsoft.com/en-us/office/dev/add-ins/excel/custom-functions-best-practices#troubleshooting
Hope that helps and we will update this when debugging is ready on for custom functions on windows desktop.

How to set Ionic's LocalStorage as synchronization?

Here's what I want: When I send a net request, hoping the intercept can add headers automatically.
So I made an intercept to realize the goal. I plan to get value by LocalStorage. Howerer the way of it's executive is asynchronous, so the request can't catch up with the storage.
What should I do next?
My code as belows:
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
console.log('into intercept');
let url = req.url;
let opuser;
this.storage.get('userInfo').then((val) => {
opuser = val.Noid; // get the value what I need
});
if (opuser === undefined) {
opuser = 'undefined';
}
const newReq = req.clone({
url: url
}).clone({
setHeaders: { opuser: 'undefined' }
});// set the headers
return next.handle(newReq).pipe(
mergeMap((event: any) => {
return of(event);
})
);
}
You should prepare your header before the interceptor will be called.
Good option:
When your application starts, get the header and store it in a provider. This way you will have synchronous/direct access to it.
opUserHeader: any;
setOpuserHeader(){
return this.storage.get('userInfo').then((val) => {
this.opUserHeader = val.Noid; // get the value what I need
});
}
getOpuserHeader(){
return this.opUserHeader ? this.opUserHeader : 'undefined';
}
and when you start the app, you can call the method from your service in app.component.ts or in the first page(your choice), and your header value will exist in memory:
headersService.setOpuserHeader().then(() => { console.log('Header is set')};
now the interceptor should look very clean and you can directly get the value:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Get the header from service, attach to the cloned request and continue
const opUserHeader = this.headersService.getOpuserHeader();
const authReq = req.clone({setHeaders: {opuser: opUserHeader}});
return next.handle(authReq);
}
Bad Option:
You can put your existing code in the localStorage promise and get the value from it for all requests but performance will be affected.
Edit for Part 2:
You don't really need the address inside the interceptor function but you should call the POST method with the correct qualifier.
A complex option can be to override the HttpClient and concatenate your address to all the methods(if you don't change the endpoint) or the easiest and most sustainable in your case would be to save the address in a service and require it everytime you need like so:
apiURL = 'http://120.XXX.XXX.XXX:81/';
getApiURL(){
return this.apiURL;
}
and everytime you call the HttpClient:
const apiURL = myService.getApiURL();
this.httpClient.post(apiURL + 'api/login', params);
Good luck!

.Net Core: Validate Anti Forgery Token with Ionic front end

I have looked all over and have found similar solutions, but nothing that matches exactly what I'm working on.
We have a .net core MVC website with an API Controller for handling requests from an ionic mobile app which we are also developing.
In most cases, adding [ValidateAntiForgeryToken] to the API controller actions works. I have gone through the process of generating the token, passing it to Ionic, and storing it in the request headers for validation.
Here is the code I am using to fetch and store the token:
static XSRF_TOKEN_KEY: string = "X-XSRF-TOKEN";
static XSRF_TOKEN_NAME_KEY: string = "X-XSRF-TOKEN-NAME";
constructor(){}
static getXsrfToken(http: HTTP) : {tokenName: string, token: string} {
let tokenName: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_NAME_KEY);
let token: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_KEY);
if(!tokenName || !token){
this.fetchXsrfToken(http);
tokenName= window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_NAME_KEY);
token = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_KEY);
}
return {
tokenName: tokenName,
token: token
};
}
private static setXsrfToken({ token, tokenName }: { token: string, tokenName: string }) {
window.sessionStorage.setItem(ValidationManager.XSRF_TOKEN_KEY, token);
window.sessionStorage.setItem(ValidationManager.XSRF_TOKEN_NAME_KEY, tokenName);
}
private static fetchXsrfToken(http: HTTP) {
let token: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_KEY);
let tokenName: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_NAME_KEY);
if (!token || !tokenName) {
let apiUrl: string = AppConfig.apiUrl + "/GetAntiforgeryToken";
http.get(apiUrl, {}, {})
.then(r => this.setXsrfToken(JSON.parse(r.data)))
.catch(r => console.error("Could not fetch XSRFTOKEN", r));
} else {
this.setXsrfToken({ token: token, tokenName: tokenName });
}
}
Here is the action in my controller that serves anti forgery tokens:
[HttpGet]
public override IActionResult GetAntiforgeryToken()
{
var tokens = _antiforgery.GetAndStoreTokens(HttpContext);
return new ObjectResult(new
{
token = tokens.RequestToken,
tokenName = tokens.HeaderName
});
}
I set the headers of the http plugin by calling this function from the view's associated typescript file:
initializeHttp() {
let token = ValidationManager.getXsrfToken(this.http);
this.http.setHeader(token.tokenName, token.token);
console.log("Http Initialized: ", token);
}
then any request I make with the http plugin is validated properly in the controller's action:
this.http.post(apiUrl, {}, {}).then(response => {
that.navCtrl.setRoot(HomePage);
});
Up to this point, everything works great. The problem arises when I try to use XmlHttpRequest to for a POST instead of the built-in http plugin:
let file = {
name: e.srcElement.files[0].name,
file: e.srcElement.files[0],
};
let formData: FormData = new FormData();
formData.append('file', file.file);
let xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.open('POST', apiUrl, true);
console.log("setting request header: ", tokenVal); //verify that tokenVal is correct
xhr.setRequestHeader("X-XSRF-TOKEN", tokenVal);
xhr.send(formData);
If I remove the [ValidateAntiForgeryToken] attribute from the controller's action, the file is posted properly. However, nothing I have tried has worked with the attribute being included.
I believe the issue has something to do with the validation tokens being added to a cookie automatically by Ionic, and the cookie is passed along with the request from the http plugin. However, XMLHttpRequest does not pass the cookie along (and is unable to do so?).
I have read up on the subject quite a bit over the past few days but I admit that this validation is still mostly a black box to me. Is there a way to validate the request in my action using only the token which is passed up in the header?
The reason I am running into this problem is that I need to upload a file, which I was unable to do using the http plugin. There are solutions for uploading images using Ionic's file-transfer plugin, but it has been deprecated and the release notes suggest using XmlHttpRequest instead.
Other things I have tried:
I have found solutions for .net standard which use System.Web.Helpers.AntiForgery for custom validation on the server, but this namespace is not included in .net core and I could not find an equivalent.
I tried many different ways to post the file using the http plugin (since it has no issues validating the antiForgery token). Everything I tried resulted in the action being hit but the file being posted was always null. A solution which uploads a file using the http plugin would also be acceptable.
Why is it that I was able to spend two full days on this problem, but as soon as I post a question about it, I find the answer? Sometimes I think the internet gods are just messing with me.
As it turns out, the native http plugin has an uploadFile() function that I never saw mentioned anywhere else. Here's what the solution does:
Use the fileChooser plugin to select a file from the phone's storage
Use the filePath plugin to resolve the native filesystem path of the image.
Use http.uploadFile() instead of http.post()
This works because as mentioned above, I was able to properly set the validation token in the http plugin's header to be accepted by the controller.
And here is the code:
let apiUrl: string = AppConfig.apiUrl + "/UploadImage/";
this.fileChooser.open().then(
uri => {
this.filePath.resolveNativePath(uri).then(resolvedPath => {
loader.present();
this.http.uploadFile(apiUrl,{ },{ },resolvedPath, "image")
.then(result => {
loader.dismiss();
toastOptions.message = "File uploaded successfully!";
let toast = this.toastCtrl.create(toastOptions);
toast.present();
let json = JSON.parse(result.data);
this.event.imageUrl = json.imgUrl;
})
.catch(err => {
console.log("error: ", err);
loader.dismiss();
toastOptions.message = "Error uploading file";
let toast = this.toastCtrl.create(toastOptions);
toast.present();
});
});
}
).catch(
e => console.log(e)
);

In an isomorphic flux application, should the REST api calls be implemented in the action?

Should it be implemented in the action creator, or in a service class or component? Does the recommendation change if it's an isomorphic web app?
I've seen two different examples:
Action creator dispatches an action login_success/login_failure after making the rest call
Component calls an api service first and that service creates a login_success or failure action directly
example 1
https://github.com/schempy/react-flux-api-calls
/actions/LoginActions.js
The action itself triggers a call to the api then dispatches success or failure
var LoginActions = {
authenticate: function () {
RESTApi
.get('/api/login')
.then(function (user) {
AppDispatcher.dispatch({
actionType: "login_success",
user: user
});
})
.catch(function(err) {
AppDispatcher.dispatch({actionType:"login_failure"});
});
};
};
example 2
https://github.com/auth0/react-flux-jwt-authentication-sample
The component onclick calls an authservice function which then creates an action after it gets back the authentication results
/services/AuthService.js
class AuthService {
login(username, password) {
return this.handleAuth(when(request({
url: LOGIN_URL,
method: 'POST',
crossOrigin: true,
type: 'json',
data: {
username, password
}
})));
}
logout() {
LoginActions.logoutUser();
}
signup(username, password, extra) {
return this.handleAuth(when(request({
url: SIGNUP_URL,
method: 'POST',
crossOrigin: true,
type: 'json',
data: {
username, password, extra
}
})));
}
handleAuth(loginPromise) {
return loginPromise
.then(function(response) {
var jwt = response.id_token;
LoginActions.loginUser(jwt);
return true;
});
}
}
What's the better/standard place for this call to live in a Flux architecture?
I use an api.store with an api utility. From https://github.com/calitek/ReactPatterns React.14/ReFluxSuperAgent.
import Reflux from 'reflux';
import Actions from './Actions';
import ApiFct from './../utils/api.js';
let ApiStoreObject = {
newData: {
"React version": "0.14",
"Project": "ReFluxSuperAgent",
"currentDateTime": new Date().toLocaleString()
},
listenables: Actions,
apiInit() { ApiFct.setData(this.newData); },
apiInitDone() { ApiFct.getData(); },
apiSetData(data) { ApiFct.setData(data); }
}
const ApiStore = Reflux.createStore(ApiStoreObject);
export default ApiStore;
import request from 'superagent';
import Actions from '../flux/Actions';
let uri = 'http://localhost:3500';
module.exports = {
getData() { request.get(uri + '/routes/getData').end((err, res) => { this.gotData(res.body); }); },
gotData(data) { Actions.gotData1(data); Actions.gotData2(data); Actions.gotData3(data); },
setData(data) { request.post('/routes/setData').send(data).end((err, res) => { Actions.apiInitDone(); }) },
};
In my experience it is better to use option 1:
Putting API calls in an action creator instead of component lets you better separate concerns: your component(-tree) only calls a "log me in" action, and can remain ignorant about where the response comes from. Could in theory come from the store if login details are already known.
Calls to the API are more centralized in the action, and therefore more easily debugged.
Option 2 looks like it still fits with the flux design principles.
There are also advocates of a third alternative: call the webAPI from the store. This makes close coupling of data structures on server and client side easier/ more compartmental. And may work better if syncing independent data structures between client and server is a key concern. My experiences have not been positive with third option: having stores (indirectly) create actions breaks the unidirectional flux pattern. Benefits for me never outweighed the extra troubles in debugging. But your results may vary.

Handling CSRF/XSRF tokens with Angular frontend and Drupal 7 backend

I'm in the process of building a new AngularJS frontend for a Drupal 7 website. This is using the Services module with session-based authentication, across two domains using CORS. I am able to authenticate with Drupal, retrieve the user object and session data, and then get the CSRF token from the services module. What I'm having trouble with is setting all this up in the header so that subsequent requests are authenticated. I understand the overall concept but am new to both AngularJS and preventing CSRF attacks.
From what I have gathered reading about this set-up with AngularJS and RubyOnRails, there can be inconsistencies between platforms concerning what the token is named and how it is processed. There also seems to be a number of suggestions on how to set this token in the header. However, I'm having trouble in finding a solid example of how to get these platforms speaking the same language.
The only thing I'm doing with my $httpProvider in app.js is:
delete $httpProvider.defaults.headers.common['X-Requested-With'];
The login controller, in controller.js:
.controller('LoginCtrl', ['$scope', '$http', '$cookies', 'SessionService', function($scope, $http, $cookies, SessionService) {
$scope.login = function(user) {
//set login url and variables
var url = 'http://mywebsite.com/service/default/user/login.json';
var postDataString = 'name=' + encodeURIComponent(user.username) + '&pass=' + encodeURIComponent(user.password);
$http({
method: 'POST',
url: url,
data : postDataString,
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}).success(function (data, status, headers, config) {
var sessId = data.sessid;
var sessName = data.session_name;
$cookies[sessName] = sessId;
var xsrfUrl = 'http://mywebsite.com/services/session/token';
$http({
method: 'GET',
url: xsrfUrl
}).success(function (data, status, headers, config) {
$cookies["XSRF-TOKEN"] = data;
SessionService.setUserAuthenticated(true);
}).error(function (data, status, headers, config) {
console.log('error loading xsrf/csrf');
});
}).error(function (data, status, headers, config) {
if(data) {
console.log(data);
var msgText = data.join("\n");
alert(msgText);
} else {
alert('Unable to login');
}
});
};
The solution has to do with how the cookies need to be set and then passed through subsequent requests. Attempts to set them manually did not go well but the solution was simpler than I expected. Each $http call needs to set the options:
withCredentials: true
Another change I made was to use the term CSRF instead of XSRF, to be consistent with Drupal. I didn't use any built-in AngularJS CSRF functionality.
addItem: function(data)
{
return $http.post('api/programs/'+$stateParams.id+'/workouts', {item:data},{
headers:
{
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-CSRF-Token': $('meta[name="xxtkn"]').attr('content')
}
});
}
since it has been a year of this topic! not sure still encountering the same problem but for the ones who comes to search for answers here is how i handle it!
Pay attention the headers{} part i define a new header and call it X-CSRF-Token and grab value from the DOM of (serverside) generated html or php. It is not a good practise to also request the csrf token from the server.Cuz attacker could somehow request that as well. Since you save it as a cookie. Attacker can steal the cookie! No need to save it in a cookie! send the token with header and read it in the serverside to match it!
and for multitab of a same page issue. I use the same token thruout the whole session.
Only regenerate on login, logout and change of major site or user settings.
There is a great library callse ng-drupal-7-services. If you use this in you project it solves authentication / reauthentication and file / node creation aut of the box and you can fokuse on the importent stuff in your project.
So Authentication is there solved like this:
function login(loginData) {
//UserResource ahndles all requeste of the services 3.x user resource.
return UserResource
.login(loginData)
.success(function (responseData, status, headers, config) {
setAuthenticationHeaders(responseData.token);
setLastConnectTime(Date.now());
setConnectionState((responseData.user.uid === 0)?false:true)
setCookies(responseData.sessid, responseData.session_name);
setCurrentUser(responseData.user);
AuthenticationChannel.pubLoginConfirmed(responseData);
})
.error(function (responseError, status, headers, config) {
AuthenticationChannel.pubLoginFailed(responseError);
});
};
(function() {
'use strict';
AuthenticationHttpInterceptor.$inject = [ '$injector'];
function AuthenticationHttpInterceptor($injector) {
var intercepter = {
request : doRequestCongiguration,
};
return intercepter;
function doRequestCongiguration (config) {
var tokenHeaders = null;
// Need to manually retrieve dependencies with $injector.invoke
// because Authentication depends on $http, which doesn't exist during the
// configuration phase (when we are setting up interceptors).
// Using $injector.invoke ensures that we are provided with the
// dependencies after they have been created.
$injector.invoke(['AuthenticationService', function (AuthenticationService) {
tokenHeaders = AuthenticationService.getAuthenticationHeaders();
}]);
//add headers_______________________
//add Authorisation and X-CSRF-TOKEN if given
if (tokenHeaders) {
angular.extend(config.headers, tokenHeaders);
}
//add flags_________________________________________________
//add withCredentials to every request
//needed because we send cookies in our request headers
config.withCredentials = true;
return config;
};
There is also some kind of kitchen sink for this project here: Drupal-API-Explorer
Yes, each platform has their own convention in naming their tokens.
Here is a small lib put together hoping to make it easy to use with different platforms. This will allow you to use set names and could be used across all requests. It also works for cross-domain requests.
https://github.com/pasupulaphani/angular-csrf-cross-domain