every time the route change, I check if is set a variable with the current logged user details, if this variable is not set, I simply redirect to the login page.
Now I'm trying to achieve some kind of "remember me" functionality, so every time the route changes, if the variable user doesn't exist instead of redirecting to the login page, I check the local storage for an "authtoken", if is set I call a check function passing the authtoken to the server, that returns the user and the app will works the same way as after the manual login (that returns the same user as the check function).
I'm pretty sure this is not the best way to do that.
If I reload the page, first thing I run the check function that sends the authtoken to the server and wait for a response, if the user exists that value is assigned to a variable in the rootscope.
I have different services that use the variable in the rootscope, for example
angular.module('myApp')
.service('AdminService', function AdminService($rootScope, $http, StorageService) {
var authtoken = StorageService.get('authtoken');
var clientId = $rootScope.loggedUser.id;
and of course when the check function runs it waits for the response, but the service is being instantiated and $rootScope.loggedUser.id does not exists.
How can I tell the app to wait until the check function receive the response?
This is my code
....
}).run(function($rootScope, $location, AuthService, StorageService) {
var intended = $location.path();
AuthService.check().success(function(data) {
if(data.user) {
$rootScope.loggedUser = data.user;
$location.path(intended);
} else $location.path('login');
});
$rootScope.$on('$routeChangeStart', function() {
if($rootScope.loggedUser) {
....
For example if the user bookmarks the page "myapp.com/#/admin/users", I don't want to redirect to the login, if I have in local storage the authtoken, but this causes the controller to be instantiated, that uses the service, that needs the $rootScope.loggedUser.id that is not yet populated.
And I want to run the function check only when the page (re)loads (not every time the user change route).
I would advise to re-examine your design if you need a service call to check your auth token. Auth tokens typically have expiration time stored in them, so you could just check whether you are within the expiration period without calling the server. You are not compromising security, since auth tokens are signed by the server, and validated when you make server calls to do anything useful. So, under normal circumstances, no separate check call is needed.
But, if you insist, this use case is best handled with the resolve property of the route. This means, however, that every route that cares about the user's logged-in state would have to have a resolve property defined. This does not mean that you have to call the service on each route change.
Instead of using $rootScope.loggedUser to store the user, have it be cached by the AuthService and injected via the resolve parameter.
So, you could do the following:
$routeProvider
.when("some/secure/route", {
controller: "SecureCtrl",
resolve: {
user: AuthService.checkUser
}
});
And in your AuthService:
...
checkUser: function(){
var deferred = $q.defer();
if (cachedUser){
deferred.resolve(cachedUser);
} else {
AuthService.check().success(
function(data){
// cachedUseris defined at AuthService's scope
cachedUser = data.user;
deferred.resolve(data.user);
});
}
return deferred.promise;
}
Then, in your controllers:
.controller("SecureCtrl", function(user){
$scope.userId = user.id;
}
Related
I am trying to integrate Keycloak login into my React app and I'm trying to get the JWT from keycloak. Here is the code:
const [keycloakState, setKeycloakState] = useState<any>();
const login = () => {
const keycloak = Keycloak("/keycloak.json");
keycloak.init({onLoad: 'login-required'}).then(authenticated => {
console.log('kk', keycloak)
console.log('at', authenticated)
setKeycloakState({ keycloak: keycloak, authenticated: authenticated });
}).catch(err => {
alert(err);
});
console.log('log after')
}
The login function is triggered when a button is clicked. It redirects properly to keycloak, I can log in and I am properly redirected to the app. The problem is that after the redirect back to the app with proper login the code in the then part of the chain is not executed, and even the 'log after' does not appear in the logs. The catch error part works fine.
Why might this be happening? I have keycloak-js added to my project.
I used to face this problem before. The way that I passed is separating the "init" function and then invoke it later.
Here is my example on jsFiddle: 'https://jsfiddle.net/gzq6j3yu/1/'
Our solution was to use the functions onAuthSuccess and onAuthError avaliable on the KeycloakInstance keycloak-js provides. (The documentation around this is a little shaky but you can find them if you check out the source code.) As the names imply these functions get called when an auth attempt is successful or unsuccessful, respectively.
note: in the following snippets this._instance replaces OP's keycloak constant.
Basic code snippet:
import Keycloak from 'keycloak-js';
...
// pulled from a class's init function from a custom Keycloak helper class so won't translate one for one but you get the point.
this._instance = Keycloak(configObject);
this._instance.onAuthSuccess = () => {
// code to execute on auth success
};
this._instance.onAuthError = () => {
// code to execute on auth error
};
this._instance.init(initOptions)
...
We also had a getter to get the token on the KeycloakInstance (or empty string) on the same class. This is an easy way to refer to the token in your code to check if it actually exists, etc. Here's what that'd look like inside the class.
get token() {
return this._instance ? this._instance.token : '';
}
Hope this can help out some folks.
I think the reason your fulfilled callback is not executed is the way your app interacts with Keycloak. You initialize the Keycloak-Adapter with onLoad: 'login-required' which will redirect the user to Keycloak - which means the Javascript execution is interrupted at this point. Keycloak will redirect the user back to your app and since you wrapped the Keycloak-Adapter in a function which is only executed when a button is clicked, the promise callback is not executed.
Simple example:
// do this on page load
keycloak.init({onLoad: 'login-required'}).then((authenticated) => {
console.log('authenticated', authenticated)
})
You will not see a "authenticated", false in your console when you open up your app. If the user is not authenticated, he will be redirected (so no chance to execute that callback). If he then comes back and is authenticated, the callback will be executed and authenticated should be true.
If you want the user to click a button, a setup like this should work:
// do this somewhere early in your App or main.js and in a way that this code is executed on page load
const keycloak = new Keycloak(configuration);
keycloak.init({onLoad: 'check-sso'}).then((authenticated) => {
if (authenticated) {
// do what needs to be done if sign in was successful, for example store an access token
} else {
// handle failed authentication
}
}).catch(err => {
alert(err);
});
const login = () => { // this could be an on-click event handler
keycloak.login();
};
check-sso won't redirect the user to Keycloak if unauthenticated, so the user can trigger the login when needed.
Keep in mind that your JavaScript code will run twice and you have to cover both cases, first the user is not authenticated and needs to be redirected to Keycloak and a second time once the user comes back from Keycloak (then we should get the information that the user is authenticated in .then().
i am developing an angular 4 application. once the user wants to log in to the system it sends a http request to the server and server validate the user and it sends a reposnce with a authentication key and other user details. i use local storage to save these information
login() {
if (this.loginForm.valid) {
this.serviceUserService.authenticate(this.loginForm).subscribe(response => {
if (response.message != "_err") {
//saving the current user in localstorage
localStorage.setItem('currentUser', JSON.stringify(response.body));
this.router.navigateByUrl('/');
} else {
alert("invalid login. Please try again")
}
}, err => {
console.log(err);
})
}
}
it seems like that localStorage.setItem() is an asynchronous function. so before it save the curent user in the local storage it redirect to the main page. but since the token is not saved in the local storage no http requests will work. how do i wait until localStorage.setItem() finish it's task and then send the user to home page.
You are wrong , all localStorage calls are synchronous. Probably you can add a check before navigating to the next page.
localStorage.setItem('currentUser', JSON.stringify(response.body));
this.router.navigateByUrl('/');
In case anyone is getting the same kind of a issue... the reason for this was I have created a variable in my service class to save the current user and set the value for that variable inside the constructor. So, in the login page when the authenticate method gets called it tries to initialize the variable but since user is not logged in yet it is still null. That was causing the issue.
I’m developing a Single Page Application with AngularJS.
When a user successfully logs in, a security token is stored in a cookie. Now, when he refreshes the page, the token will be sent to the backend, which returns a JSON object "currentUser" containing all the relevant information about the current user (as name, access-groups, profile picture, etc.).
The problem is, this is an asynchronous process of course, so when the controller starts another operation, say, just alerting the user’s name, this value will be undefined at that time.
Of course, I could set a timeout but is there a better solution?
I thought about a "currentUserService", which initializes first (sending the cookie and filling the user information with the backend response) and can only be processed after this initialization is completed.
But how can this be done? Or are there any other possibilities?
edit:
Hi guys,
thanks for the input!
Both of your suggestions seem to be very promising for asynchronous requests in general, but I think they might not fit perfectly for my concern:
The information about the current user only have to be requested once, so I would like to store them for the whole application (e.g. in the rootScope or a service) accessible from any controller without having to request them again in every controller (as in the callback or resolve-solution) but make sure that there won’t be any „timeout“ problems. Do you have any ideas?
You can resolve the user's data before the view loads either with ng-route or ui-router:
This example is written for ui-router:
.state('profile', {
url: '/profile',
controller: 'profileCtrl as vm',
resolve: {
user: function(AuthService) {
//Return a promise or an object to be resolved.
return AuthService.getUserFromToken(); //Say this is asynchronous and returns a promise
}
}
});
//In controller:
.controller('profileCtrl', function(... , user) {
//User data available here
this.user = user;
});
Please note if any errors arise during the resolve stage the state will not be loaded so you'll have to take care of the errors!
If a user refreshes you have to initialize everything. I assume the token is stored in localstorage or something and I assume this is angular 1.*. To do this I think you should call user-related functions from your http call-callback:
$scope.user = {};
$scope.getUser = function(){
$http({
method: 'GET',
url: '/someUrl'
}).then(function (res) {
$scope.user = res.data; //or whatever the response is
$scope.handleUserRelatedThings();
}).catch(function(err) {
//handle error
})
}
$scope.handleUserRelatedThings = function(){
//do things with $scope.user
alert($scope.user.name);
}
//on init
$scope.getUser();
I am looking for a better way to do the transitions between route states in angularjs. Currently I have been following a couple different tutorials to configure angular with a backend api server using authentication on the server side.
http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/
https://vickev.com/#!/article/authentication-in-single-page-applications-node-js-passportjs-angularjs
Both of these describe a similar to identical solution to authentication on server side rather than on the client side. I have a Auth service in angular that uses a method to post to the api server checking whether the user isLoggedIn using an api key.
isLoggedIn: function( event, toState ) {
var user_api = {};
if( "user" in $rootScope ) {
user_api = { "api": $rootScope.user.api };
}
$http.post('http://bac-api/authenticated/', user_api)
.success( function( user ) {
if( toState.url === '/login' && user.authenticated === true) {
$state.transitionTo('dashboard');
}
})
.error( function( user ) {
if( user.authenticated === false ) {
event.preventDefault();
$state.transitionTo('login');
}
});
},
If the api key is not present of course the user is not logged in and the server will send a {"autheticated": false} 401 response. If the user has an api key it uses that api key to check on the server whether it is valid (logged in) or not valid (not logged in).
In order to catch the routes and check whether a user is authenticated I and using "stateChangeStart"
$rootScope.$on("$stateChangeStart", function ( event, toState, toParams, fromState, fromParams ) {
Auth.isLoggedIn( event, toState );
});
The first problem with this is that a state change is not triggered on the intial page load and results in using the routeProvider to transition to the login page. And then throughout the app if a route that is requested is not in the configured routes it will transition to the login page but not trigger a stateChangeStart. So the user could be logged in and sitting at the login page. I would rather it transfer to the dashboard as my configured route.
For the most part this setup seems to be working ok in theory but the routes are choppy. So what will happen is in between checking if the user is logged in the route will start to change and start to show the page but then when the application realizes the user is not logged it will make the switch to the login page. I would like to resolve this choppiness. And instead of getting a glipse of the other pages be able to transfer correctly.
I am using ui-router and states in the application for everything. And then using the Route Provider only to do $routeProvider.otherwise('login') when there isn't a route for the requested route.
I would like to figure out how to stop showing part of new pages (the choppiness) during route transitions while the application is checking for the user being authenticated. Whether that is in a different event that I'm unaware of or whatever.
Also a better way to use routeProvider.otherwise() to trigger a state change and check whether the user isLoggedIn. If logged in transfer to the dashboard ifnot logged in stay on login page until finished logging in.
Sorry if this is so confusing. I am new to angularjs and have been reading everything I can find about routes, states and authentication. This is a new concept for me to learn how to manage authentication on the server side with angular.
-- Edit
After taking the recommendation of MBielski I have implemented resolve into the states to use the Auth.isLoggedIn() service. But this still has not removed the choppiness of switching routes and I even fear that once this is not in local development the choppiness will become worse waiting on the api response. Here is the resolve code for my parent dashboard state.
'dashboard-shell': {
name: 'dashboard-shell',
resolve: {
Auth: function( Auth ) {
return Auth.isLoggedIn();
}
},
views: {
"dashboard-shell": {
controller: 'DashboardController',
templateUrl: 'app/modules/template-manager/partials/dashboard.tpl.html'
}
}
},
Your problem seems to be that every state change results in a POST request that must be handled before you can switch states. I assume you do that to handle session expiry, or because you don't have an authentication cookie to inspect.
You've basically implemented a pessimistic approach to ensure the user is authenticated before doing any other HTTP callbacks.
Another approach might be to use the angular-http-auth HTTP interceptor. This interceptor provides an optimistic approach to ensure the user is authenticated: it just passes every HTTP call on to the backend, but you give it a special callback; when the backend returns a 403 Unauthorized this callback is used to e.g. show a login dialog so the user can (re)authenticate. After that, the interceptor replays all HTTP calls that resulted in a 403.
The ui-router has a resolve option that should clear up the chop. https://github.com/angular-ui/ui-router/wiki. Anything that is contained in the resolve section will be completely resolved before the rest of the route changes happen and the pages load.
isLoggedIn: function( event, toState ) {
var user_api = {};
if( "user" in $rootScope ) {
user_api = { "api": $rootScope.user.api };
}
return $http.post('http://bac-api/authenticated/', user_api);
},
resolve: {
var logged = Auth.isLoggedIn();
logged.then(function(data){
//success
}, function(data){
//failure
});
}
I'm currently writing a server-centric package for Meteor, and the relevant code looks something like this:
__meteor_bootstrap__.app.stack.unshift({
route: route_final,
handle: function (req,res, next) {
res.writeHead(200, {'Content-Type': 'text/json'});
res.end("Print current user here");
return;
}.future ()
});
This is obviously a relatively hacky way of doing things, but I need to create a RESTful API.
How can I access Meteor.userId() from here? The docs say it can only be accessed from inside a method or publish. Is there any way around that?
Things I've tried:
Capture it from a publish using Meteor.publish("user", function() { user = this.userId() });
Get the token + user id from the cookies and authenticate it myself using something like Meteor.users.findOne({_id:userId,"services.resume.loginTokens.token":logintoken});
Create a method called get_user_id and call it from inside my code below.
The thing that you need to target first is that to get something that can identify the user from headers (especially because you want to get the username at a point where no javascript can run).
Meteor stores session data for logins in localStorage, which can only be accessed via javascript. So it can't check who is logged in until the page has loaded and the headers have been passed.
To do this you need to also store the user data as a cookie as well as on localStorage:
client side js - using cookie setCookie and getCookie functions from w3schools.com
Deps.autorun(function() {
if(Accounts.loginServicesConfigured() && Meteor.userId()) {
setCookie("meteor_userid",Meteor.userId(),30);
setCookie("meteor_logintoken",localStorage.getItem("Meteor.loginToken"),30);
}
});
server side route
handle: function (req,res, next) {
//Parse cookies using get_cookies function from : http://stackoverflow.com/questions/3393854/get-and-set-a-single-cookie-with-node-js-http-server
var userId = get_cookies(req)['meteor_usserid'];
var loginToken = get_cookies(req)['meteor_logintoken'];
var user = Meteor.users.findOne({_id:userId, "services.resume.loginTokens.token":loginToken});
var loggedInUser = (user)?user.username : "Not logged in";
res.writeHead(200, {'Content-Type': 'text/json'});
res.end("Print current user here - " + loggedInUser)
return;
}.future ()
The cookie allows the server to check who is logged in before the page is rendered. It is set as soon as the user is logged in, reactively using Deps.autorun
My solution was inspired by the server part of #Akshat's method. Since I'm making a RESTful API, I just pass the userId/loginToken in every time (either as a param, cookie or header).
For anyone interested, I bundled it as a package: https://github.com/gkoberger/meteor-reststop