Passport Session Authentication on Android

7

I have a web app running with NodeJS, Express and authentication with Passport-JS, and everything works perfectly.

Now I'm developing an Android app and I need to authenticate my users using the same API.

From what I understand by reading the documentation and questions in the gringo OS, passport creates a cookie in the client browser with the logged in user ID, and this cookie is passed on all requests from the client to the server. The server in turn can decode this cookie and arrow the req.user with this id from the cookie, to know who is requesting the API.

My question is, how can I do this manually on Android, since this process does not occur automatically as in the browser? If you can not use this strategy, what do I need to change?

Links to tutorials or blogs that help you are also very welcome!

For reference, here's the part of the API that matters in this case:

app.use(session({
    secret : 'hidden of course :)',
    resave: false,
    saveUninitialized: true
}));

app.use(passport.initialize());
app.use(passport.session());

/****** Passport functions ******/
passport.serializeUser(function (user, done) {
    done(null, user.idUser);
});

passport.deserializeUser(function (id, done) {
    db.user.findOne( { where : { idUser : id } }).then(function (user, err) {
        done(null, user);
    });
});

//Facebook
passport.use(new FacebookStrategy({
    //Information stored on config/auth.js
    clientID: *******,
    clientSecret: ******,
    callbackURL: *******,
    profileFields: ['id', 'emails', 'displayName', 'name', 'gender', 'picture.type(large)'] 

}, function (accessToken, refreshToken, profile, done) {
    //Using next tick to take advantage of async properties
    process.nextTick(function () {
        db.user.findOne( { where : { idUser : profile.id } }).then(function (user, err) {
            if(err) {
                return done(err);
            } 
            if(user) {
                return done(null, user);
            } else {
                // Check whether the email is undefined or valid
                var emailTemp = '';
                if(profile.emails && profile.emails[0] && profile.emails[0].value) {
                    emailTemp = profile.emails[0].value;
                } else {
                    emailTemp = '';
                }

                var picture = '';
                if(profile.photos && profile.photos[0] && profile.photos[0].value) {
                    picture = profile.photos[0].value;
                } else {
                    picture = '/img/profile.png';
                }

                var sexFb = '';
                if(profile.gender) {
                    sexFb = profile.gender;
                } else {
                    sexFb = '';
                }

                // Create the user
                db.user.create({
                    idUser : profile.id,
                    token : accessToken,
                    picture : picture,
                    nameUser : profile.displayName,
                    email : emailTemp,
                    sex : sexFb
                }).then(function () {
                    db.user.findOne( { where : { idUser : profile.id } }).then(function (user, err) {
                        if(user) {
                            return done(null, user);
                        } else {
                            return done(err);
                        }
                    });
                });
            }
        });
    });
}));

app.use(express.static(__dirname + '/public/'));

/* FACEBOOK STRATEGY */
// Redirect the user to Facebook for authentication.  When complete,
// Facebook will redirect the user back to the application at
//     /auth/facebook/callback//
app.get('/auth/facebook', passport.authenticate('facebook', { scope : ['email']}));
/* FACEBOOK STRATEGY */
// Facebook will redirect the user to this URL after approval.  Finish the
// authentication process by attempting to obtain an access token.  If
// access was granted, the user will be logged in.  Otherwise,
// authentication has failed.

app.get('/auth/facebook/callback',
    passport.authenticate('facebook', { failureRedirect: '/' }),
    function (req, res) {
        // Successful authentication, redirect home.
        res.redirect('../../app.html');
});     

And here's an example of use, to show what I need to have (the req.user):

app.put('/profile', function (req, res) {
    //Updates the profile information of the user
    db.user.update({
        nameUser : req.body.nameUser
    }, {
        where : {
            idUser : req.user.idUser
        }
    }).then(function (user) {
        res.json({ yes : "yes" });
    });
});
    
asked by anonymous 14.12.2016 / 13:05

1 answer

0

The best way to keep session, cache and other things of the user is through an interface that saves the data in the user's preference. To do this First add a user agent in your webview for the web application to know where the access is coming from.

private static final String ANDROID_USER_AGENT = "AppAndroid";
WebSettings settings = webView.getSettings();
String userAgent = settings.getUserAgentString();
if (!userAgent.endsWith(ANDROID_USER_AGENT)) {
    userAgent += " " + ANDROID_USER_AGENT;
}
settings.setUserAgentString(userAgent);

After that, create an interface in your application

public class PreferencesJavascriptInterface {

@JavascriptInterface
public void setData(String key, String data) {
    PreferenceUtils.set(key, data);
}

@JavascriptInterface
public String getData(String key) {
    return PreferenceUtils.get(key);
}

}

This PreferenceUtils is a class that saves and takes the data that you send to that interface.

public class PreferenceUtils {

public static final String PREFERENCES_NAME = "APP_PREFERENCES";

private static SharedPreferences sharedPreferences;

public static void init(Context context) {
    sharedPreferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
}

public static void set(String key, String value) {
    sharedPreferences.edit().putString(key, value).apply();
}

public static String get(String key) {
    return sharedPreferences.getString(key, null);
}
}

In your application you will have to start this class of preferenceutils, like this:

PreferenceUtils.init(this);

In your webview you will have to add this interface as follows:

webView.addJavascriptInterface(new PreferencesJavascriptInterface(), "Android");

On your front you will check if the user is accessing an android device from the user agent.

You will need to add this javascript

 NativeBridge
var NativeBridge = {
  callbacksCount: 1,
  callbacks: {},
  // Automatically called by native layer when a result is available
  resultForCallback: function (callbackId, resultArray) {
    try {
      var callback = NativeBridge.callbacks[callbackId];
      if (!callback) return;
      callback.apply(null, resultArray);
    } catch (e) {
      console.log(e);
    }
  },
// Use this in javascript to request native objective-c code
// functionName : string (I think the name is explicit :p)
// args : array of arguments
// callback : function with n-arguments that is going to be called when the native code returned
  call: function (functionName, args, callback) {
    var hasCallback = callback && typeof callback == "function";
    var callbackId = hasCallback ? NativeBridge.callbacksCount++ : 0;
    if (hasCallback) {
      NativeBridge.callbacks[callbackId] = callback;
    }
    var iframe = document.createElement("IFRAME”);
//esse nome é “native-brigdge-js” deve ser combinado conosco, ou mantém esse ou nos dê um para deixar igual
    iframe.setAttribute("src", "native-bridge-js:" + functionName + ":" + callbackId + ":" + encodeURIComponent(JSON.stringify(args)));
// For some reason we need to set a non-empty size for the iOS6 simulator...
    iframe.setAttribute("height", "1px");
    iframe.setAttribute("width", "1px");
    document.documentElement.appendChild(iframe);
    iframe.parentNode.removeChild(iframe);
    iframe = null;
  }
};

if (test()) {
  NativeBridge.call('share', args, callback);
}
//verificar se é um aparelho android/ios
//Isso pode ser substituído por uma verificação de vocês!
//Essas Strings BaseWebviewAppIOS e BaseWebviewAppAndroid devem ser configuradas no Android ou no iOS
// para identificar que se trata dos nossos apps
// Isso se vocês forem usar esse método de verificação
var test = function () {
  return /[BaseWebviewAppIOS|BaseWebviewAppAndroid]$/.test(navigator.userAgent);
};
var testIOS = function () {
  return /BaseWebviewAppIOS$/.test(navigator.userAgent);
};
var testAndroid = function () {
  return /BaseWebviewAppAndroid$/.test(navigator.userAgent) && Android != undefined;
};
//ex: de funcoes pra salvar nos shared preferences no ANDROID
// salva e pega strings chave-valor
var get = function (key) {
if (testAndroid() && key != undefined) {
  var data = Android.getData(key);
  if (data != undefined) {
    return JSON.parse(data);
  }
 }
};
var set = function (key, data) {
  if (testAndroid()) {
    if (key != undefined) {
      if (data != undefined) {
        data = JSON.stringify(data);
     }
     Android.setData(key, data);
   }
  }
};
var getObject = function (key) {
  if (testAndroid()) {
//verifica se é um dispositivo android? se sim, usa os sharedpreferences
    return get(key);
  } else {
//se não, usa os cookies nativos do browser/iOS //(esse exemplo usa Angular)
    return $cookies.getObject(key);
  }
};
var putObject = function (key, data) {
  if (testAndroid()) {
//verifica se é um dispositivo android? se sim, usa os sharedpreferences
    set(key, data);
  } else {
//se não, usa os cookies nativos do browser/iOS
    $cookies.putObject(key, data);// (esse exemplo usa Angular)
  }
};
var remove = function (key) {
  if (testAndroid()) {
//verifica se é um dispositivo android? se sim, usa os sharedpreferences
   set(key, null);
  } else {
//se não, usa os cookies nativos do browser/iOS
   $cookies.remove(key); // (esse exemplo usa Angular)
  }
};

Example call:

<html>
<body>
<script>
function getFireBaseToken() {
NativeBridge.call("firebase", null, function (data) {
alert(data);
console.log(data);
//no caso vocês podem fazer algo mais interessante, enviar pra api de vocês, por exemplo
});
}

function saveSomeCookies() {
set('COOKIE', Math.random())
}

function getSomeCookies() {
cookies = get('COOKIE');
alert(cookies);
console.log(cookies);
}
</script>

<button onclick="getFireBaseToken()">Firebase Button</button>
<button onclick="saveSomeCookies()">Salvar Cookie</button>
<button onclick="getSomeCookies()">Pegar Cookies</button>

</body>
</html>

To learn more about how this interface works in webview documentation talk about it: link

    
30.12.2016 / 15:05