Authentication in jQuery Mobile and PhoneGap - authentication

I have a web application built with jQuery Mobile and PHP (CodeIgniter framework). Now I'm trying to make a PhoneGap version of it as well, to make it distributable as a standalone app. However, the PHP web app. version uses Ion Auth, a CodeIgniter plugin for authentication. So when you go to a page that requires authentication, the app redirects you to the authentication controller login method. And after authentication it redirects you back to the home page (the jQuery Mobile page in this case). This works fine in the web app., since the home page is opened by the home controller in the first place anyway.
But here's the crux: in the PhoneGap version, the "home" page needs to be the index.html file in PhoneGap. Apparently you can load another url on startup by adding a value in PhoneGap.plist, but that is not acceptable by apple for submitting to app store. And if I do a redirect in the authentication, I can't get back to the index.html file after authentication...
So how should one go about authentication in a PhoneGap/jQuery Mobile app?
UPDATE:
I have tried this according to one of the answers, but the app still tries to navigate to the account/login page (which doesn't exist), when I just want to login through the post and return a value from the method:
$('#login_form').bind('submit', function () {
event.preventDefault();
//send a post request to your web-service
$.post('http://localhost/app_xcode/account/login', $(this).serialize(), function (response) {
//parse the response string into an object
var response = response;
//check if the authorization was successful or not
if (response == true) {
$.mobile.changePage('#toc', "slide");
} else {
alert('login failed');
$.mobile.changePage('#toc', "slide");
}
});
});
Here's the controller method:
function login()
{
//validate form input
$this->form_validation->set_rules('identity', 'Identity', 'required');
$this->form_validation->set_rules('password', 'Password', 'required');
$base_url = $this->config->item('base_url');
$mobile = $this->detect_mobile();
if ($mobile === false && $base_url != 'http://localhost/app_xcode/') //Only restrict if not developing
redirect('account/notAMobile');
else if ($this->form_validation->run() == true) { //check to see if the user is logging in
//check for "remember me"
$remember = (bool)$this->input->post('remember');
if ($this->ion_auth->login($this->input->post('identity'), $this->input->post('password'), $remember)) { //if the login is successful
//redirect them back to the home page
$this->session->set_flashdata('message', $this->ion_auth->messages());
echo true;
/*redirect($this->config->item('base_url'), 'refresh');*/
}
else
{ //if the login was un-successful
//redirect them back to the login page
$this->session->set_flashdata('message', $this->ion_auth->errors());
/*redirect('account/login', 'refresh');*/ //use redirects instead of loading views for compatibility with MY_Controller libraries
}
}
else
{ //the user is not logging in so display the login page
//set the flash data error message if there is one
$this->data['message'] = (validation_errors()) ? validation_errors()
: $this->session->flashdata('message');
$this->data['identity'] = array('name' => 'identity',
'id' => 'identity',
'type' => 'text',
'value' => $this->form_validation->set_value('identity'),
);
$this->data['password'] = array('name' => 'password',
'id' => 'password',
'type' => 'password',
);
}
}
I think I have removed or commented out any redirects that were there. So I don't know why it tries to load the view still? Does it have something to do with jQuery Mobile trying to navigate there because I post to that url?

The reason your form is still submitting and it's trying to change pages is because you have a syntax error in your submit handler Javascript. On line two, event is not defined so trying to call event.preventDefault() errors. Although the handler fails, the browser still submits the form using it's default action and method.
Either change your function signature to function(event) { or simply return false from the function. Returning false is equivalent to preventing default.
$('#login_form').bind('submit', function () {
//send a post request to your web-service
$.post('http://localhost/app_xcode/account/login', $(this).serialize(), function (response) {
//check if the authorization was successful or not
if (response == true) {
$.mobile.changePage('#toc', "slide");
} else {
alert('login failed');
$.mobile.changePage('#toc', "slide");
}
}, 'JSON');
return false;
});

You can make requests to your web-service (Ion Auth) from your app. with jQuery. Your login would look something like this:
//add event handler to the `submit` event for your login form
$('#login_form').bind('submit', function () {
//send a post request to your web-service
$.post('http://my-domain.com/my-auth/auth.php', $(this).serialize(), function (response) {
//parse the response string into an object
response = $.parseJSON(response);
//check if the authorization was successful or not
if (response.status === 'success') {
//do login here
} else {
//do error here
}
});
});
$(this).serialize() will add the login form's data to the post request. This example assumes your web-service will return JSON.

Have you looked at PhoneGap Plugin: ChildBrowser (iPhone, Android, other) and its locChanged method?
I have only coded apps that use OAuth (Twitter App) and OpenID (AppLaud App) for PhoneGap / Android, but the child browser plugin has what's needed for those. Sounds like Ion Auth may be similar: after auth driven by provider, return user to app seamlessly.

Related

Infinite page reload with Vue when redirecting to external authentication URL

Here is what I'd like to implement in my Quasar 2 / Vue 3 application:
User visits any route.
Upon each route change, a backend call is made to check if the user is still authenticated.
If the user is not authenticated, she shall get redirected to the external URL of the authentication provider.
This is my current approach:
router.beforeEach(() => {
api.get('/api/user/current').then((response) => {
userInfoStore.authenticated = true;
userInfoStore.givenName = response.data.givenName;
userInfoStore.fullName = response.data.fullName;
}).catch(() => {
userInfoStore.authenticated = false;
});
if (!userInfoStore.authenticated) {
redirect('http://localhost:8555/oauth2/authorization/keycloak');
}
});
However the following problems occur:
The app constantly reloads itself.
The call to /api/user/current gives NS_BINDING_ABORTED in the browser console.
The console shows Changed to http://localhost:8555/oauth2/authorization/keycloak but gives this error message.
ChunkLoadError: Loading chunk src_layouts_MainLayout_vue failed.

Nuxt don't see a authenticated user

I am doing user authentication, but I ran into a problem. First, when loading, the middleware is loaded, it does not see the authorized user through $fire.auth.onAuthStateChanged. And if you go to another page (without reloading page), user is appear. How to make him see it at the first boot?
Here is my middleware
export default function ({app, route, redirect}) {
console.log('middleware')
app.$fire.auth.onAuthStateChanged(user => {
if (user) {
console.log('user+')
if (route.path === perm.signin || route.path === perm.signup) {
return redirect('/')
}
} else {
console.log('user-')
if (route.path !== perm.signin || route.path !== perm.signup) {
return redirect(perm.signin)
}
}
})
}
and what I received (1st pic) when first enter to app in console.log(middleware, user-). But if I go to another page I receive middleware, user+.
I need user + to be on the first start
onAuthStateChanged fires the callback you provided as argument after the middleware has run.
In other words the callback's return statements are not being run when the middleware runs.
You could either ensure the middleware is called after the first authentication attempt, but this would slow down the initial startup of the application. So you could expose the nuxt router to the onAuthStateChanged handler and router.push('/login') or router.push('/somewhere') from there.

Implementing Google One Tap Login using angular

navbar.template.html
<div id="g_id_onload"
data-client_id="832#################m921.apps.googleusercontent.com"
data-cancel_on_tap_outside="false"
data-login_uri="http://localhost:3010/auth/g-one-tap"
data-callback="handleCredentialResponse">
</div>
The API get the response I am able to validate the user and return the validated JWT token, how can I capture the response and avoid the redirection of the page to http://localhost:3010/auth/g-one-tap
How can I us some click function to be used in typsecript file which can help in following the normal login flow which I am using earlier with google login button.
public socialSignIn(responseData) {
this.googleSubscription = this._globalService.googleLogin(responseData)
.subscribe(
data => {
if (data['success']) {
const token = data['data']['token'];
if (this.platformId === 'browser') {
// login to save the token
}
}
},
error => {
console.log('error');
}
);
}
As mentioned here, you should not use both data-login_uri and data-callbck attributes at the same time.
You need to remove the data-login_uri attribute in you code.
And povide an implementation for the callback function (whose name is handleCredentialResponse in your code) if not yet.

Refresh MVC view after logging in using Angular

Working with the Breeze Angular SPA template found here, http://www.breezejs.com/samples/breezeangular-template, I'm trying to update a menu that changes after user authenticates.
My example is slightly different from the default template in that I've moved the Login and Register views into modal windows. When the modal closes after a successful login, the menu, which is in the MVC View (and not the Angular View) does not update as a complete page refresh does not occur.
In the SPA template, authentication is required before entering the SPA, then a hard redirect/refresh occurs and the SPA is loaded. In my case, you could be browsing views/pages in the SPA before authenticating.
MVC View Code Snippet (Views/Home/Index.cshtml)
...
<li>
#if (#User.Identity.IsAuthenticated)
{
User Logged In: #User.Identity.Name
}
else
{
User Logged In: Annon
}
</li></ul>
<div ng-app="app">
<div ng-view></div>
</div>
....
I have working the root redirect, after login, the page hard refreshes if json.redirect is set to '/'. However, if its set to the current page, i.e. '#/about', Angular handles the routing and therefore no hard refresh occurs, thus the menu is not updated.
Ajax Login Code Snippet (App/ajaxlogin.js)
... part of login/register function
if (json.success) {
window.location = json.redirect || location.href;
} else if (json.errors) {
displayErrors($form, json.errors);
}
...
Is this possible to do using my current setup? Or do I need to move the menu somewhere inside the SPA and use Angular to determine what menu to show? If the latter, direction in how to best do this? I'm new to both Angular and Breeze.
The TempHire sample in Breeze has a really good way of handling authentication for a SPA (in my opinion at least!) Granted this is using Durandal so you will need to adapt it to Angular, but they are both frameworks doing the same basic principles so good luck! -
Basically, the Controller action has an annotation [Authorize] on the action that the prepare method is calling on the entitymanagerprovider. If a 401 is returned (not authorized) the SPA takes the bootPublic path and only exposes a login route to the user. When the login is successful, the login method tells the window to reload everything, at which time the authorization passes, and the bootPrivate method is called -
shell.js (Durandal, but should be adaptable)
//#region Internal Methods
function activate() {
return entitymanagerprovider
.prepare()
.then(bootPrivate)
.fail(function (e) {
if (e.status === 401) {
return bootPublic();
} else {
shell.handleError(e);
return false;
}
});
}
function bootPrivate() {
router.mapNav('home');
router.mapNav('resourcemgt', 'viewmodels/resourcemgt', 'Resource Management');
//router.mapRoute('resourcemgt/:id', 'viewmodels/resourcemgt', 'Resource Management', false);
log('TempHire Loaded!', null, true);
return router.activate('home');
}
function bootPublic() {
router.mapNav('login');
return router.activate('login');
}
login.js -
function loginUser() {
if (!self.isValid()) return Q.resolve(false);
return account.loginUser(self.username(), self.password())
.then(function() {
window.location = '/';
return true;
})
.fail(self.handleError);
}
The account.loginUser function is basically just an ajax call that passes credentials to the account controller and returns a success or failure. On success you can see the callback is fired for window.location = '/' which does a full reload. On failure simply show an alert or something.

How to hide templates with AngularJS ngView for unauthorized users?

I have a basic PHP app, where the user login is stored in the HTTP Session. The app has one main template, say index.html, that switch sub-view using ngView, like this
<body ng-controller='MainCtrl'>
<div ng-view></div>
</body>
Now, this main template can be protected via basic PHP controls, but i have sub-templates (i.e. user list, add user, edit user, etc.) that are plain html files, included from angular according to my route settings.
While i am able to check for auth what concern the request of http services, one user is able to navigate to the sub-template url and access it. How can i prevent this from happen?
I would create a service like this:
app.factory('routeAuths', [ function() {
// any path that starts with /template1 will be restricted
var routeAuths = [{
path : '/template1.*',
access : 'restricted'
}];
return {
get : function(path) {
//you can expand the matching algorithm for wildcards etc.
var routeAuth;
for ( var i = 0; i < routeAuths.length; i += 1) {
routeAuth = routeAuths[i];
var routeAuthRegex = new RegExp(routeAuth.path);
if (routeAuthRegex.test(path)) {
if (routeAuth.access === 'restricted') {
return {
access : 'restricted',
path : path
};
}
}
}
// you can also make the default 'restricted' and check only for 'allowed'
return {
access : 'allowed',
path : path
};
}
};
} ]);
And in the main/root controller listen for $locationChangeStart events:
app.controller('AppController', ['$scope', '$route', '$routeParams', '$location', 'routeAuths',
function(scope, route, routeParams, location, routeAuths) {
scope.route = route;
scope.routeParams = routeParams;
scope.location = location;
scope.routeAuth = {
};
scope.$on('$locationChangeStart', function(event, newVal, oldVal) {
var routeAuth = routeAuths.get(location.path());
if (routeAuth.access === 'restricted') {
if (scope.routeAuth.allowed) {
event.preventDefault();
}
else {
//if the browser navigates with a direct url that is restricted
//redirect to a default
location.url('/main');
}
scope.routeAuth.restricted = routeAuth;
}
else {
scope.routeAuth.allowed = routeAuth;
scope.routeAuth.restricted = undefined;
}
});
}]);
Demo:
plunker
References:
angularjs services
location
UPDATE:
In order to fully prevent html template access then it's best done on the server as well. Since if you serve the html from a static folder on server a user can access the file directly ex: root_url/templates/template1.html thus circumventing the angular checker.
If you want to block them from going to that page create a service: http://docs.angularjs.org/guide/dev_guide.services.creating_services
This service can be dependency injected by all your controllers that you registered with the routeParams.
In the service you can would have a function that would check to see if the person is logged in or not and then re-route them (back to the login page perhaps?) using http://docs.angularjs.org/api/ng.$location#path. Call this function in each of the controllers like so:
function myController(myServiceChecker){
myServiceChecker.makeSureLoggedIn();
}
The makeSureLoggedIn function would check what current url they're at (using the $location.path) and if it's not one they're allowed to, redirect them back to a page that they are allowed to be.
I'd be interested to know if there's a way to prevent the routeParams from even firing, but at least this will let you do what you want.
Edit: Also see my answer here, you can prevent them from even going to the page:
AngularJS - Detecting, stalling, and cancelling route changes