0

Using ExtJS 6.20 with a bunch of models with fixed urls to an API that look like:

Ext.define('Admin.model.Patient', {
    extend: 'Ext.data.Model',

    requires: [
        'Ext.data.proxy.Rest',
        'Ext.data.schema.Association'
    ],

    fields: [
        { name: 'id', type: 'string' },
        { name: 'mrn', type: 'string' },
        { name: 'birth_date', type: 'date', format: 'Y-m-d' },
        { name: 'sex', type: 'string' },
        { name: 'first_name', type: 'string' },
        { name: 'last_name', type: 'string' }
    ],

    proxy: {
        type: 'ajax',
        url: 'http://localhost/patientview/api/read',
        reader: {
            type: 'json',
            rootProperty: ''
        }
    }

});

The issue is that an authentication session cookie needs to be obtained from the API upon a successful login before I can connect to the API's protected resources. I am easily able to grab the session cookie from the HTTP response (and plan on stashing it in session storage), but am not sure how to pass this as payload (as extraParams?) to the proxy, especially since it's already bound to my data models when the app is launched.

FWIW, for testing the API with an authenticated cookie, I am using the cookie parameter in curl as in curl -v --cookie "session=XXX" http://0.0.0.0:5000/patientview/api/read, where XXX is the value of the session cookie.

Since the Set-Cookie header is not generally available due to it being a 'forbidden' object (see for example, how to get a cookie from an AJAX response).

It does look like there is a way to make these credentials available through subsequent requests, using the withCredentials: true keyword (see getting extjs to work with cors and cookies on chrome).

I modified my login to the API as per this:

onLoginButton: function() {

    var formPanel = this.lookupReference('loginForm'),
        form = formPanel.getForm(),
        url = 'http://localhost/login_api/';

    if (form.isValid()) {
        values = form.getValues(true);

        console.log(values);

        Ext.Ajax.request({
            method: 'POST',
            cors: true,
            withCredentials: true,
            useDefaultXhrHeader: false,
            url: url,
            params: values, //username and password
            headers: {
                'Accept': 'application/json'
            },
            disableCaching: false,

            success: function (response) {
                json = Ext.decode(response.responseText);
                test = response.getAllResponseHeaders();
                console.log('json');
                console.log(json);
                console.log(test);
                console.log(response);

                if (response.status === 201) {
                    console.log(json.items);

                }
                else {
                    Ext.MessageBox.alert('Error', response.message);
                }
            }
        });

    }

},

But, even with this, it is not working as expected. I am assuming that since third party cookies are not available in the responseHeaders and since they should be injected through use of the withCredentials keyword, that something else needs to be done.

FWIW, my ExtJS app has the path: http://localhost/test/, while the Flask/API is accessible under http://localhost/... (To eliminate any cross-domain issues, I'm running these locally on an nginx/uwsgi web/app server, which serves these up over port 80). I DO get a successful login, btw. I am just not sure how to now pass the credentials to my other API calls (e.g., my Patient model above).

NB: it works if I nest another AJAX call (as per the blog post above) to http://localhost/patientview/api/read within my first Ext.request.Ajax call's success callback, as per

success: function (response) {

    if (response.status === 201) {
       console.log(json.items);
       Ext.Ajax.request({
           url: 'http://localhost/patientview/api/read',
           method: 'GET',
           cors: true,
           useDefaultXhrHeader: false,
           withCredentials: true,
           success: function(response) {
              var result = Ext.decode(response.responseText);
              console.log(result);

           }
        });

    }
}

But, while this is proof of concept that this is feasible, it does not solve the issue of how to inject the credentials into my predefined models.

EDIT

I removed the cors and withCredentials options, since the web app and api are on same domain, etc. It did work fine. Also, I tested another function in which I am calling Ext.request.Ajax and that too worked by grabbing the credentials automagically from the browser.

It still is not working with the model proxies (which are all defined similar to that above; same protocol, same domain, same port, etc.)

Of note though, if I refresh the browser, then the data is made available to the all the different stores bound to the models with ajax proxies (which makes sense). I am wondering if there is a way to reload the stores bound to the model after I get a success on login?

My stores are bound to a model view as per:

Ext.define('Admin.view.main.MainModel', {
    extend: 'Ext.app.ViewModel',
    alias: 'viewmodel.main',

    stores: {
        patients: {
            model: 'Admin.model.Patient',
            autoLoad: true
        }
    }
});

And then, for example, via data binding it is fed to a dropdown combobox, with a bind command:

bind: {
    store: '{patients}'
},

If I were to do a store reload, where exactly would this go? I am kind of new to data binding in ExtJS.

horcle_buzz
  • 2,101
  • 3
  • 30
  • 59
  • How does your api expect to receive the session token? In the URL of each api call? – Kevin Collins Jul 27 '17 at 21:42
  • See edited comment above. – horcle_buzz Jul 27 '17 at 22:33
  • I'm using Flask, and from what I can tell, the http request object has an attribute cookies that is a dict object, e.g., the printed output from the request.cookies object looks like: `{'session': u'eyJfZnJlc2giOnRydWUsIl9pZCI6eyIgYiI6Ik1HVTBNbVV4TmpJek16a3‌​paV1kxWVdFek1URmlOV0‌​kxWTJRMVkySTVPVFU9In‌​0sInVzZXJfaWQiOiIxIn‌​0.DFwToA.wcZs_tSGInd‌​16V2AUddBg5Y0XuE'}` – horcle_buzz Jul 28 '17 at 01:15
  • If your api and web app are on the same hostname and port then you won't need the cors or withCredentials configs. – Kevin Collins Jul 28 '17 at 21:19
  • And if your api is responding with the session cookie in the cookies header of the login response, then I think that cookie would be sent with every subsequent api request, like it is in your nested ajax call example. Which means you shouldn't have to manage it at all. – Kevin Collins Jul 28 '17 at 21:20
  • The question is how to apply the same method outside the nesting. Resources that are not in this are not using the credentials. – horcle_buzz Jul 28 '17 at 21:37
  • Outside of the nesting it should still work because the browser keeps track of the cookie and sends it with subsequent requests. – Kevin Collins Jul 28 '17 at 21:38
  • Hm. Are the model proxies pointing to the same protocol, hostname, and port as the login url? – Kevin Collins Jul 28 '17 at 22:10
  • As noted, this works if I call an Ext.request.Ajax call outside of the nesting, but does not work with model proxies (they are all of the same form). I will add more detail above that may be relevant. – horcle_buzz Jul 29 '17 at 01:26
  • Please check whether the auth cookie is restricted to a specific path. – Kevin Collins Jul 29 '17 at 01:37
  • Nope, not restricted: see above. I think it has to do with a store reload. Since the data are bound to controls (comboboxes, grids, etc.), and since when the app is initially launched the data are not yet available, then once I authenticate I need to refresh the browser to make the credentials available to those other stores. Does it sound feasible that a store reload would fix this? – horcle_buzz Jul 29 '17 at 01:43
  • 1
    Ah. It's due autoload true on the stores. Without you'll need to call store.load at the appropriate point in your controllers. Or, you could execute the auth request in an onbeforeroute, and only continue if you get a success. – Kevin Collins Jul 29 '17 at 01:49
  • 1
    Check out section on route handling here https://docs.sencha.com/extjs/6.5.0/guides/application_architecture/router.html – Kevin Collins Jul 29 '17 at 01:52
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/150478/discussion-between-horcle-buzz-and-kevin-collins). – horcle_buzz Jul 29 '17 at 14:52
  • 1
    FYI, I just found this blog post by Sencha Dude, Mitchell Simoens: https://sencha.guru/2015/07/02/handling-user-session-with-routes/ ... it's perfect, since one of the comments was a question on how to apply this to the admin dashboard (which is the template I am using for my web app). Way cool! Thanks man! – horcle_buzz Jul 29 '17 at 17:15
  • This seems workable, but when I check for a session, even when logged out of the API, I get a success due to non-expired cookies (even tried clearing them from my browser to no avail). I may post a separate thread for this later if I can't figure this out. – horcle_buzz Jul 31 '17 at 16:40

2 Answers2

0

To set an authorization cookie, globally, once, you can try to use

Ext.util.Cookies.set('session', token);

However, if the resource is on a different endpoint, this fails, because the cookies are saved for the endpoint the index.html is under, and are not submitted to a different endpoint. In that case, you have to check your remote API how else it may take the token.

Because of these cross-domain issues, if you were using a standardized authentication method like OAuth, the token would usually be expected as a Bearer token, which meant that you could set it once, globally, for all your Ajax calls, like this:

Ext.Ajax.setDefaultHeaders({
    'Authorization': 'Bearer ' + token,
    'Accept': 'application/json'
});
Alexander
  • 19,906
  • 19
  • 75
  • 162
  • Thanks. I'll play around with this tomorrow. (Same domain, different paths.) I assume that `Ext.state.CookieProvider` would not do any good, and that only generates cookies? – horcle_buzz Jul 27 '17 at 23:17
  • All was well until I tried getting `Set-Cookie` from the AJAX response headers and then found out that I cannot do this due to security restrictions. I am editing and modifying my question above based on preliminary research. – horcle_buzz Jul 28 '17 at 19:41
0

I ended up finally using a modified version of this: ExtJS login app... it was much easier to implement than trying to manage routing behavior based on a user session. Now, all my models with ajax proxies do not load until the main view gets created, which only happens after a success callback in my AJAX authentication request.

horcle_buzz
  • 2,101
  • 3
  • 30
  • 59