PomeGranate PomeGranate - 4 months ago 106
Node.js Question

Passport.js sessions are not set when Login with Android

In my AuthController.js I check if login is sucessful and set a hashcode in the session:

req.logIn(user, function (err) {
if (err) res.send(err);
var redirectTo = req.session.redirectTo ? req.session.redirectTo : '/user/show/'+user.id;
delete req.session.redirectTo;

bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(user.email, salt, function (err, hash) {
if (err) {
console.log(err);
} else {


req.session.passport.user_type = user.type;
req.session.passport.user_avatar = user.avatar;
req.session.passport.email = user.email;
req.session.passport.token = hash;

// here session is set! also when login with Android
console.log("User passport Sessions: ",req.session.passport)

res.json(user);
res.end();

}
});
})

})


The Sessions are set when I login with a browser - I check them with another simple controller:

'who': function (req, res) {
res.json(req.session);
},


When I login with my Android App (Volley Request) everything work as expected, but when I then after the successful login make a new request to this 'who' controller, the whole 'passport' attribute is null.

Why is the passport session not set, when I log in with Android App?

The Question is: Are volley requests different than browser requests? When the Server can't save the session for volley requests, how do I on the server know ( on the second request) that the user is already logged in?

I dump the req.headers, this is how Chrome looks like:

{ host: 'localhost:1337',
connection: 'keep-alive',
'cache-control': 'max-age=0',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'accept-encoding': 'gzip, deflate, sdch',
'accept-language': 'de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4',
cookie: '__utma=111872281.1508283337.1455636609.1455636609.1455636609.1; __utmz=111872281.1455636609.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); _ga=GA1.1.1508283337.1455636609; sails.sid=s%3AHNW3D3ktwA79IFHH4gX9Ko6o73MZOjRK.I7sTYkCKSkkwset6OC2ap58fcROPtV6PqUnkaInGW44',
'if-none-match': 'W/"1f3e-WoreHgYUy3uvXGNH++ttsQ"' }


And this is how Android request over Volley looks like:

{ 'user-agent': 'Dalvik/2.1.0 (Linux; U; Android 6.0; Android SDK built for x86 Build/MASTER)',
host: '10.0.2.2:1337',
connection: 'Keep-Alive',
'accept-encoding': 'gzip' }
{ 'if-none-match': 'W/"1f3e-WoreHgYUy3uvXGNH++ttsQ"',
'user-agent': 'Dalvik/2.1.0 (Linux; U; Android 6.0; Android SDK built for x86 Build/MASTER)',
host: '10.0.2.2:1337',
connection: 'Keep-Alive',
'accept-encoding': 'gzip' }


The session part is missing - meanwhile I know that you have to implement the session management by yourself. Is there a simple Tutorial for multiple HTTP requests over Volley with sessions?

Answer

Ok 10 days later I know how to "fix" this.

When you make a request to your Server, you get a bunch of things back. The header is the important thing for us.

Read more about HTTP Headers here: http://code.tutsplus.com/tutorials/http-headers-for-dummies--net-8039

HTTP requests are stateless, that means, it doesn't save informations and states about the client.

Volley doesn't handle headers. What we have to do is:

When we make the first Request from our App, we have to save the response header informations.

{"Access-Control-Allow-Credentials":"","Access-Control-Allow-Headers":"","Access-Control-Allow-Methods":"","Access-Control-Allow-Origin":"","Access-Control-Expose-Headers":"","Connection":"keep-alive","Content-Length":"235","Content-Type":"application\/json; charset=utf-8","Date":"Tue, 09 Aug 2016 22:39:40 GMT","ETag":"W\/\"eb-bxZXjFIir0ihzYNPgoUbTg\"","set-cookie":"sails.sid=s%3AsjQL1FsAJY5VWGHdgChpa0hgZPs06AH9z.H1RLNh%2FMqLjxdGtKtnWFv8cFO8WjeAUhPFQp2Yl73Lo; Path=\/; HttpOnly","Vary":"X-HTTP-Method-Override","X-Android-Received-Millis":"1470713694296","X-Android-Response-Source":"NETWORK 200","X-Android-Selected-Protocol":"http\/1.1","X-Android-Sent-Millis":"1470713694119","X-Powered-By":"Sails <sailsjs.org>"}

the important information in the header is the "set-cookie" part. We have to save this information from the first call, and reuse it on the every next requests, so that the server knows who we are.

How?

I used this custom Request from djodjo. The important method is the parseNetworkResponse: http://stackoverflow.com/a/36435582/3037960

And we use that Class like this:

mHeaders = new HashMap<String, String>(); 
    session = new SessionManager(myView.getContext());

 registerRequest = new MetaRequest(1, MY_ENDPOINT, obj, new Response.Listener<JSONObject>() {

                    @Override
                        public void onResponse(JSONObject response) {

                            JSONObject headers = new JSONObject();
                            try {
                                headers.put("headers", response.get("headers"));
   // get the "set-cookie" value
                                String sailsSession = String.valueOf(headers.getJSONObject("headers").get("set-cookie"));
                                Log.d("Response Headers", sailsSession);

                                // Session Manager
                                session.storeSailsSession(sailsSession);

                                String cookie = session.getSailsSession();

                                if( cookie != null) {
                                   //just to test if the cookie is now saved?
                                    Log.d("Cookies", cookie);
                                }

                            } catch (JSONException e) {
                                e.printStackTrace();
                            }

                        }
                    }, new Response.ErrorListener() {

                        @Override
                        public void onErrorResponse(VolleyError error) {
                                Log.d("ERROR", String.valueOf(error));
                            // TODO Auto-generated method stub

                        }
                    }){

                        public Map<String, String> getHeaders() {
                            return mHeaders;
                        }
                    };

                    RequestQueue queue = Volley.newRequestQueue(getActivity());
                    queue.add(registerRequest);

You see there the line with the session manager... session.storeSailsSession(sailsSession); This is a custom Class for saving our cookie in the shared preference, so that we can get this information overall in the app when we need them.

public class SessionManager {
    // Shared Preferences
    SharedPreferences pref;

    // Editor for Shared preferences
    Editor editor;

    // Context
    Context _context;


    // User hash/cookie
    public static final String KEY_SESSION = "session";

    // Constructor
    public SessionManager(Context context){
        this._context = context;
        pref = _context.getSharedPreferences(PREF_NAME, 0);
        editor = pref.edit();
    }


    public void storeSailsSession(String session){
        editor.putString(KEY_SESSION,session);
        editor.commit();

       }
 public String getSailsSession(){
        return pref.getString(KEY_SESSION,null);
    }

    }

You see now how to get the cookies from the header, how to store them for later; and now for the nex request how to set the header?

Before you send your request(you don't have to use the custom MetaRequest from above) you have to get your session from the shared preference and then set it in the hash map mHeaders:

String sailsCookie =  session.getSailsSession();
mHeaders.put("cookie", sailsCookie.toString());

and don't forget to return that mHeaders to your request, like this:

JsonArrayRequest arrayreq = new JsonArrayRequest(JsonURL,Listener,ErrorListener){
                public Map<String, String> getHeaders() {
                    return mHeaders;
                }

normally you only use new WhatEverRequest(....); but now you have to attach the { ... return mHeaders;}

I hope you understand it, otherwise just make a comment, I'll explain it. For me it worked, so I'm sure it will also work for you. For the next time I recommend you to use https://jwt.io