I have the following code, which I am using successfully in another program
index.html
Eat Fresh
</html>
init.js
function init() {
CLIENT_ID = "927885761089.apps.googleusercontent.com"
SCOPES = ["https://www.googleapis.com/auth/userinfo.email"]
gapi.client.load('oauth2', 'v2', signin)
}
function signin() {
gapi.auth.authorize({client_id: CLIENT_ID, scope: SCOPES, immediate: true}, userAuthed)
}
function userAuthed() {
console.log(gapi.auth.getToken())
console.log(gapi.auth.getToken().accessToken)
gapi.client.oauth2.userinfo.get().execute(function(resp) {
checkEmail(resp)
})
}
function checkEmail(user) {
var validEmail = (whiteList.indexOf(user.email) !== -1)
if (!user.code && validEmail) {
startApp()
} else {
displayError(user.email)
}
}
gapi.auth.getToken() is returning null and to my knowledge that means that my I am not logged in. I don't know how to refresh the login or force a log out. Any help would be appreciated.
Changing the the following
gapi.auth.authorize({client_id: CLIENT_ID, scope: SCOPES, immediate: false}, userAuthed)
will prompt the user to give permission to the app, thus allowing for an authorized access token and for the remainder of the code to work.
Related
My angular JS application is for an e-commerce usecase. There would be several pages, where some data would be fetched from some REST APIs which would be authenticated (and some not requiring authentication). If authentication fails (user not logged in), the APIs would all respond with a special error_code (say 'AUTH_FAIL').
My requirement is if any API fails due to authentication, then a login modal form dialog should appear in that page. This modal form contains the Username and password field. If the login succeeds, the modal window should close, and the current route should be re-freshed.
I understand how to do this for a particular route/controller. However, since there would be a lot of such pages where this would be needed, I'm unable to think of a way in which same piece of code could be easily utilized, since in my opinion, this does seem like a common requirement. How can it be done, or if not, what's the best way around it?
You can use interceptors for this purpose. Inteceptors can be used for global error handling, authentication, or any kind of synchronous or asynchronous pre-processing of request or postprocessing of responses.
For example I use the following code to redirect user to login when authentication fails.
.factory('myInterceptor', ['$q', '$location', '$injector', function ($q, $location, $injector) {
return {
response: function (response) {
return response || $q.when(response);
},
responseError: function (rejection) {
if (rejection.status === 401) {
var stateService = $injector.get('$state');
stateService.go('login');
}
return $q.reject(rejection);
}
}
}])
.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('myInterceptor');
}]);
Using interceptors sounds like the most obvious and elegant solution, however I was never satisfied with it, mostly because of running into the circular dependency problems.
Here are some bits and pieces of logic from one of my apps using angular 1.6 and ui-router.
Some explanation about the business logic before you deep dive into the code.
I use JWT authentication and my server expects JWT to be passed as a header, hence the specifics of the authService implementation. The authService checks if the header is expired, and tries to send a JWT refresh request before actually showing a login dialog. Feel free to adjust it to your security implementation (e.g. session cookie/based or some other storage).
authService.js
This service is responsible for storing security token in the client. It returns a promise, which is resolved with the JWT token (if present or if it was refreshed). The promise is rejected when the token is expired and the service failed to obtain new token from the server.
app.factory('authService', function($http, $q, $window, jwtHelper, API_HOST) {
var storage = $window.localStorage;
var cacheToken = {};
var targetUrl = null;
function saveToken(data) {
var tokenPayload = jwtHelper.decodeToken(data.auth_token);
storage.setItem('auth_token', data.auth_token);
storage.setItem('refresh_token', data.refresh_token);
storage.setItem('exp', tokenPayload.exp);
storage.setItem('user_identifier', tokenPayload.user_identifier);
cacheToken.auth_token = storage.getItem('auth_token');
cacheToken.refresh_token = storage.getItem('refresh_token');
cacheToken.exp = storage.getItem('exp');
cacheToken.user_identifier = storage.getItem('user_identifier');
}
function setCacheToken() {
cacheToken.auth_token = storage.getItem('auth_token');
cacheToken.refresh_token = storage.getItem('refresh_token');
cacheToken.exp = storage.getItem('exp');
cacheToken.user_identifier = storage.getItem('user_identifier');
}
function isAuthenticated() {
return cacheToken.auth_token && cacheToken.exp > moment(new Date().getTime()).unix()
}
setCacheToken();
return {
saveToken: function(data) {
saveToken(data);
return cacheToken;
},
getToken: function() {
return cacheToken;
},
isAuthenticated: isAuthenticated,
targetUrl: targetUrl,
getAuthorizationHeader: function() {
if (isAuthenticated()) {
return $q.when({
'Authorization': 'Bearer ' + cacheToken.auth_token
});
} else {
cacheToken.auth_token = storage.getItem('auth_token');
cacheToken.refresh_token = storage.getItem('refresh_token');
cacheToken.exp = storage.getItem('exp');
if (isAuthenticated()) {
return $q.when({
'Authorization': 'Bearer ' + cacheToken.auth_token
});
} else {
if (!cacheToken.refresh_token) return $q.reject(null);
return $http.post(API_HOST + '/tokens/refresh', {
'refresh_token': cacheToken.refresh_token
}).then(function(response) {
saveToken(response.data);
return {
'Authorization': 'Bearer ' + cacheToken.auth_token
};
}).catch(function() {
cacheToken = {};
$window.localStorage.clear();
return $q.reject(null);
})
}
}
}
}
});
app.run block
This piece of logic is responsible for memorising the target url in case user tried to access protected resource, or when user token/session is expired. Please 2 things here: authService.targetUrl stores the URL and authenticate property on the ui-router state is used to check if the state is protected (e.g. if the authentication logic should be applied).
$transitions.onBefore({
to: function(state) {
return state.self.authenticate;
}
}, function(trans) {
return authService.getAuthorizationHeader().then(function() {
return null;
}).catch(function() {
authService.targetUrl = $window.location.href;
$('#login-modal').modal();
return trans.router.stateService.target('homepage');
});
});
login modal directive
This piece of code stores the user token after login and also checks if the targetUrl is present in the authService, e.g. if a user tried to access protected resource some time before.
scope.loginCallback = function(response) {
authService.saveToken(response.data);
jasprApi.User.me().then(function(response) {
$rootScope.user = response.data;
$(element).modal('hide');
if (authService.targetUrl) {
$window.location.href = authService.targetUrl;
authService.targetUrl = null;
}
});
};
routes.js
Here is the ui-router states config which specified if the state should be protected
.state('admin', {
url: '/admin',
//other configuration
//...
//...
authenticate: true
})
api.js
A bonus — this is the sample from the file with the methods for accessing the API. Please note how authService is used here.
updatePageAction: function() {
return authService.getAuthorizationHeader().then(function(authHeader) {
return $http({
method: 'PUT',
url: '/admin/page/update',
headers: authHeader
});
});
},
I hope it helps!
Cheers
If a user has several accounts in their Google, the user has to choose an account as it doesn't remember which account the user chose previously.
I have managed to make an OAuth2 authentication using these codes and the configurations guided from https://developers.google.com/api-client-library/javascript/features/authentication#specifying-your-client-id-and-scopes
this.authenticate = function(){
gapi.load('client:auth2',authorize);
}
function authorize(){
gapi.client.setApiKey(API_KEY);
gapi.auth2.init({
client_id: CLIENT_ID,
scope: SCOPES
}).then(function(authResult){
var auth2 = gapi.auth2.getAuthInstance();
auth2.signIn().then(afterSignIn);
});
}
function afterSignIn(){
console.log('authenticated successfully');
$rootScope.authenticated = true;
gapi.client.load('drive', 'v3',function(){
$rootScope.$broadcast('authenticated');
});
}
I have tried these options of GoogleAuth.signIn():
auth2.signIn({
'prompt': '**promtOption**'
})...
none : it doesn't authenticate
login: does the same as select_account
consent: does the same as select_account, it additionally asks for offline use permission...
select_account: same problem as signing in without options
How can I make the program remember the user selection?
Calling auth2.signIn() will always prompt the user to sign in, even if they are already signed in. Before doing that, check to see if they are already signed in using auth2.currentUser.get().isSignedIn(). Here's a modified version of your code:
function authorize(){
gapi.client.setApiKey(API_KEY);
gapi.auth2.init({
client_id: CLIENT_ID,
scope: SCOPES
}).then(function(authResult){
var auth2 = gapi.auth2.getAuthInstance();
var user = auth2.currentUser.get();
if (user.isSignedIn()) {
afterSignIn();
} else {
auth2.signIn().then(afterSignIn);
}
});
}
I'm using lb-services from Angular JS SDK to create my default service, and currently, I have some trouble in logout & rememberMe function.
I'm following this tutorial : Angular Javascript SDK & Loopback getting started intermediate , and the way I create my authentication is as same as the example, with some adjustment to apply them in my application.
I'm not sure how to describe it, but here's some of the code for my logout & remember function
in my stateProvider, I have this kind of state as my root application
.state('app', {
url:'/',
abstract: true,
views:{
'bodyLogin': {templateUrl : 'views/login/body.html',
controller : 'BodyController'},
'bodyMain': {templateUrl : 'views/main/body.html',
controller : 'BodyController'}}
})
.state('app.mainUnreg', {
url:'home',
views: {
'content#app': {
templateUrl : 'views/login/home.html'
}}})
.state('app.mainReg', {
url:'mainMenu',
views: {
'header#app': {
templateUrl : 'views/main/header.html'
},
'sidebar#app': {
templateUrl : 'views/main/sidebar.html'
},
'middle#app': {
templateUrl : 'views/main/home.html'
},
'footer#app': {
templateUrl : 'views/main/footer.html'
}},
controller: 'HomeController'})
index.html
<div ng-if="!loggedIn" ui-view="bodyLogin" class="site-wrapper" ui-sref-active="app.mainUnreg"></div>
<div ng-if="loggedIn" ui-view="bodyMain" ui-sref-active="app.mainReg"></div>
so, if I haven't not logged in, I will enter to bodyLogin, which means I will show my login template, and otherwise, if I have logged in, I will enter to bodyMain
I have succeed to logged in and enter to my main page, but on here, and it should be have been authenticated, because in my bodyController,
.controller('BodyController', [..., function(...){
//this line is to check whether the user has been authenticated or not
if(Account.isAuthenticated() === false) {
$scope.loggedIn = false;}
// ------------
if($scope.loggedIn === false){
$scope.layout = 'login';
$state.go('app.mainUnreg');
}
else{$scope.loggedIn = true;
$scope.layout = 'styles';
$state.go('app.mainReg');
}}])
.controller('LoginController', [..., function (...) { $currentUserId = Account.getCurrentId();
$scope.$stateParams = $stateParams; $scope.loginData = {}; $scope.doLogin = function() { AuthService.login($scope.loginData)
.then(function() {
$scope.loggedIn = true;
location.reload(); }); };}])
.controller('LogoutController', [..., function(...) {
AuthService.logout()
.then(function() {
console.log("Logging out . . .");
$scope.loggedIn = false;
$state.go('app'); }); }]);
And some additional service beside lb-services.js to handle my authentication,
function login(loginData) {
var params = { rememberMe: loginData.rememberMe };
var credentials = {
email: loginData.email,
password : loginData.password
};
return User
.login(params, credentials)
.$promise.then(function(response){ $rootScope.currentUser = {...};
console.log($rootScope.currentUser);
},
function(response) {
console.log(response);
});
}
function logout(){
return User
.logout()
.$promise.then(function(){
$rootScope.currentUser = null;
});
};
I think, with this kind of code, especially I have checked with
if(Account.isAuthenticated() === false){...}
and succeed to enter my main page, I have successfully logged in and authenticated, haven't I?
But, if I tried to put ng-show="Account.isAuthenticated()" in the top div of my main page app.mainReg , my page can't be show, yet it means that my account haven't authenticated, even the loopback have successfully save my token, user id, and rememberMe boolean in local storage, like this
$LoopBack$accessTokenId = ....
$LoopBack$currentUserId = ...
$LoopBack$rememberMe = true // automatically become true?
So, do anyone know how to solve this problem? I think the problem is on the authentication.
I'm quite confused with this one, I have tried to search for solution, yet I could find any.
I just realized why I couldn't use rememberMe function, that's just because I made a silly mistake, by putting " ; " after my logout function in my additional services
And after several testing, It seems that my problem was inside my state provider. I tweaked my controller and my state provider and finally it has been work successfully.
I've got the following code snippet:
// Google functions
var clientId = 'clientid';
var apiKey = 'apikey';
var scopes = 'https://www.googleapis.com/auth/calendar';
function handleClientLoad() {
// Step 2: Reference the API key
gapi.client.setApiKey(apiKey);
gapi.client.load('calendar', 'v3', function () {
window.setTimeout(checkAuth, 1);
});
}
function checkAuth() {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true}, handleAuthResult);
}
function handleAuthResult(authResult) {
console.error(authResult.error);
if (authResult && !authResult.error) {
if (!$('#authWait').is(':visible')) {
$('#start').on("click", function () {
$('#main').hide();
$('#wizard').removeClass("hidden");
});
} else {
$('#authWait').addClass("hidden");
$('#wizard').removeClass("hidden");
}
fillCalendarList();
ga('send', 'event', 'weergaveResultaat', 'action');
} else {
$('#start').on("click", function () {
$('#main').hide();
$('#authWait').removeClass("hidden");
handleAuthClick();
})
}
}
I've replaced the clientid and apikey. The exact snippet above has been working for 2 years in the exact same configuration. As of yesterday however I'm getting back 'immediate failed' right after the handleAuthResult.
I couldn't find any details of changes Google made since yesterday. I've tried switching immediate to false, but that didn't solve anything.
After the immediate failed I get 'Uncaught TypeError: Cannot set property 'apiVersion' of undefined'.
Anyone know what is causing this?
I'm trying to persist the Firebase user authentication state across multiple pages.
$scope.loginGoogle = function() {
console.log("Got into google login");
ref.authWithOAuthPopup("google", function(error, authData) {
$scope.loggedIn = true;
$scope.uniqueid = authData.google.displayName;
}, {
remember: "sessionOnly",
scope: "email"
});
};
function checkLogin() {
ref.onAuth(function(authData) {
if (authData) {
// user authenticated with Firebase
console.log("User ID: " + authData.uid + ", Provider: " + authData.provider);
} else {
console.log("Nope, user is not logged in.");
}
});
};
However, when the checkLogin function is called in another page, authData is not defined, even though the user has logged in on the login page. What seems to be the matter?
There are two things to know here.
First, you're using the JS Client auth methods in conjunction with AngularFire. While this is not a bad thing, you need to be aware of a few gotchas.
Second, you can use the $firebaseAuth module in AngularFire 0.9 to not deal with all of the crazy stuff below.
When using Firebase JS client level functions, Angular will not always pick up on them due to its digest loop. This is true for any external JS library. The way to get around this is to use the $timeout service.
CodePen
// inject the $timeout service
app.controller("fluttrCtrl", function($scope, $firebase, $timeout) {
var url = "https://crowdfluttr.firebaseio.com/";
var ref = new Firebase(url);
$scope.loginGoogle = function() {
console.log("Got into google login");
ref.authWithOAuthPopup("google", function(error, authData) {
// wrap this in a timeout to allow angular to display it on the next digest loop
$timeout(function() {
$scope.loggedIn = true;
$scope.uniqueid = authData.google.displayName;
});
}, {
remember: "sessionOnly",
scope: "email"
});
});
});
By wrapping the $scope properties in the $timeout another cycle will be run and it will display on the page.
Ideally, you don't want to deal with this yourself. Use $firebaseAuth module built into AngularFire. You need to upgrade to the 0.9 version to use the module.