Passport Session authentication on Android

Asked

Viewed 361 times

10

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

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

From what I understand by reading the documentation and questions in the gringo OS, the Passport creates a cookie in the client’s browser with the logged in user ID, and this cookie is passed in all requests from the client to the server. The Server in turn can decode this cookie and arrow req.user with this id coming 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 it is not possible to use this strategy, what I need to change?

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

For reference, here is 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 is an example of use, to show what I need to have (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" });
    });
});
  • Does this link help?: https://scotch.io/tutorials/easy-node-authentication-setup-and-local

  • @seamusd I’ve already done this tutorial, it doesn’t help because my problem is not with Autenticacao on the web, it works, but in an android app using the same api. But thanks for the suggestion.

  • This should help you http://stackoverflow.com/questions/28447076/nodejs-express-passport-going-mobile#Answer-30770389

1 answer

1

The best way to maintain session, cache and other user things is through an interface that saves the data in the user’s preference. To do this First add a user agent to your webview so the web application knows where 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 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 preferenceutils, thus:

PreferenceUtils.init(this);

In your webview you will have to add this interface this way:

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

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

You’ll have 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)
  }
};

Call example:

<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 better know how this interface works in the webview documentation talks about it: https://developer.android.com/guide/webapps/webview.html

Browser other questions tagged

You are not signed in. Login or sign up in order to post.