I have been fumbling around with different implementations and ideas to get this to work, but I feel like I am not doing this as DRY or smart as I could be. I've been following this "tutorial" Angular Auth
So, I have a fully functional laravel (4.2) back end set up with some resource routes protected by the oauth filter. I am using the password grant and everything is working just fine there. I've got log in/out routes also set up and am able to sign in to my Ionic app and obtain and access_token and refresh_token from laravel just fine. Obtaining new access_tokens using the refesh_token works just fine as well. BUT, I am having some issues trying to figure out how to correctly handle the following things in Ionic:
What I Tried
In the article I mentioned earlier, he sets up a rootScope watcher in the run module which watches for the statechangestart event like so.
$rootScope.$on('$stateChangeStart', function (event, next) {
var authorizedRoles = next.data.authorizedRoles;
if (!AuthService.isAuthorized(authorizedRoles)) {
event.preventDefault();
if (AuthService.isAuthenticated()) {
// user is not allowed
$rootScope.$broadcast(AUTH_EVENTS.notAuthorized);
} else {
// user is not logged in
$rootScope.$broadcast(AUTH_EVENTS.notAuthenticated);
}
}
});
I am not using roles so when I implemented this I just had something like this
$rootScope.$on('$stateChangeStart', function(event, next) {
if (next.url != "/login") {
AuthService.isAuthenticated().then(function() {
console.log('you are already authed an logged in and trying to access: ' + next.url);
}, function() {
event.preventDefault();
console.log('YOU DO NOT HAVE A VALID ACCESS TOKEN');
$location.path('/app/login');
});
}
});
isAuthenticated() just hits a route inside my oauth filter so if it throws back an error (401 for example), I know that the access_token is bad. I then have a private method also inside my AuthService service that tries to get a new access_token using the users stored refresh_token
function useRefreshToken() {
console.log('Using refresh token to get new token:');
var deferred = $q.defer();
$http({
method: 'POST',
url: base_url.dev.url + 'oauth/access_token',
data: $.param({
grant_type: 'refresh_token',
client_id: API.client_id,
client_secret: API.client_secret,
refresh_token: $localStorage.session.refresh_token
}),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).success(function(data) {
console.log('refresh token worked!');
$localStorage.session.access_token = data.access_token;
$localStorage.session.refresh_token = data.refresh_token;
deferred.resolve();
}).error(function(error) {
console.log('refresh token failed');
CurrentUserService.setLogged(false);
console.log(JSON.stringify(error));
deferred.reject(error);
});
return deferred.promise;
};
If the above method returns back a rejected promise I just assume (which may be a good idea or not??) that the refresh token has expired and thus the user needs to log back in and retrieve a new access & refresh token pair from my laravel oauth/access_token route.
So the above methods have been working fine on their own, in that I am able to check if the users access_token is valid and if not retrieve a new access_token just fine using the users refresh_token.
Here's my isAuthenticated method in case you wanted to see that as well. It's a public method inside of my AuthService service.
isAuthenticated: function() {
console.log('Checking if token is still valid.');
var deferred = $q.defer();
$http.get(base_url.dev.url + 'valid-token', {
params: {
access_token: $localStorage.session.access_token
}
}).success(function(data) {
console.log('Token is still valid.');
CurrentUserService.setLogged(true);
deferred.resolve();
}).error(function(error) {
console.log(JSON.stringify(error));
useRefreshToken().then(function() {
deferred.resolve();
}, function(error) {
deferred.reject(error);
});
});
return deferred.promise;
}
The big problem I was running into is that because the AuthService.isAuthenticated() method runs async, the state the app was changing to, say PHOTOS, would be hit before isAuthenticated returns and if we have Case: 1 mentioned at the beginning of my post, the PHOTOS state will try to use an invalid access_token to try and consume a resource on my back end BEFORE the isAuthenticated method is able to get a new access_token using the refresh_token.
Now I was able to avoid the above issue by using a resolve on EVERY state which handled using the isAuthenticated method to check the access_token and get a new one if need be BEFORE consuming a resource. BUT that felt horribly unDRY. I apologize for the length of this post but I wanted to make sure you guys knew everything that was going on and what I was trying to accomplish.
I appreciate any feedback, criticism and instruction! Thanks guys.