I am running a Google App Engine project where everytime the user takes an action I want to check to see if the user is 1)logged in 2)an admin. This is the code I have for the appuser:
class AppUser
{
private UserService userService;
private User user;
public AppUser()
{
userService = UserServiceFactory.getUserService();
user = userService.getCurrentUser();
}
public IsAdministrator()
{
if(IsLoggedIn())
{
return userService.IsUserAdmin();
}
return false;
}
public IsLoggedIn()
{
return user == null;
}
}
When I log out with my app this works fine. However, if I log out on another page (like on google calendars or something) the app still thinks I'm logged in. Is there another better way to check if the user is still logged in?
Also I know that this can be done with security-constraint in the web.xml however that will not work in this case as I need to take certain actions if the user has logged off.
I am using App Engine SDK 1.7 and GWT SDK 2.4
Two ways to notify app about user logging out:
Synchronously - server actively notifies client (browser) about log-out. Use Channels API to send push notification to client. There is a GWT wrapper.
Asynchronously - server notifies client about log-out when client makes communication to server, i.e. in every RPC call add authentication check. If user id logged-out, raise an exception, which can be handled by GWT.
I ran into this today, though it was worse: I'd logged out as user A (from a Google Sites page), and logged in as user B, but my GAE app still thought I was logged in as user A. Argh.
The reason for this is that there are two cookies involved, one for tracking which Google user is logged into Google, and another for tracking which GAE application user is logged into my GAE application. Recall that a GAE could be using any federated authentication service, not just Google's. My application has no access to the google.com cookies, so I can't directly check whether user A is still logged in (or which user is currently logged in).
Unfortunately, I've not yet found a straight forward "federated logOUT" mechanism, though it is possible that Google Identity Toolkit can be used for detecting that the expected user is no longer logged in.
I found others discussing this issue:
How to manage multiple accounts login and logout in different browser pages?
UserService retrieves wrong user after logout
Update
I came up with a solution that works for my application, where I have a page that redirects the user, a student, to his or her classroom's home page. Since this is accessed infrequently by any one student (a few times a day), but which needs to know which student is logged in, I took the following approach which works for me:
User goes to page A, which clears the ACSID and SACSID cookies, and redirects to Google for the user to login.
User is probably already logged in, so Google (with several redirects) updates the ACSID and SACSID cookies to the currently logged in user, and redirects back to my application at page B.
Page B finally takes action on behalf of the logged in user, "confident" that the correct user is logged in (to the extent that pages are confident). ;-)
Here's a code sketch of the approach:
# My BaseHandler has a clear_cookie
class LoginAndRedirectHandler(base_handler.BaseHandler):
def get(self):
self.clear_cookie('ACSID')
self.clear_cookie('SACSID')
self.clear_cookie('dev_appserver_login')
if 'continue' in self.request.params and \
self.request.params['continue'].startswith('/'):
url = self.request.params['continue']
else:
# Whatever your page is that needs an up to date logged in user
url = users.create_login_url('/PageB')
if isinstance(url, unicode):
url = url.encode('utf8')
logging.info('Redirecting to ' + url)
self.redirect(url)
return
The reason I said infrequently above is that this process is expensive in time, with at least 4 or 5 redirects involved.
Related
To introduce my current setup, I have 3 components:
Website with some static (CMS) and dynamic parts, a couple of Single Page Applications, everything works on domain spa.com.
REST API, which provides any dynamic functionality (user profile) and is hosted on api.com and requires SSO tokens to work.
SSO server is hosted under sso.com domain
I'd like to determine in my SPA whether user has an active SSO session, so when I'm logged in but I do not have tokens, I can:
do a redirection dance to get tokens
do not allow user to use registration form
I'm using Keycloak JS adapter, which uses iframe mechanism and checks for KEYCLOAK_SESSION cookie and communicates with host window via some flags:
var cookie = getCookie('KEYCLOAK_SESSION');
if (cookie) {
data.loggedIn = true;
data.session = cookie;
}
The problem is that I can not rely on this cookie to check if user is logged in because the cookie has 30 day expiration date, which is way more than an actual Keycloak session lasts, let's say 15 minutes.
This cookie is being set when user gets authenticated and is not refreshed on each token update. I do not understand then it's purpose and the purpose of this flag.
I'm also thinking about relying on local storage and keeping refresh & access token there to determine if user is logged in, either by checking timeout or trying to refresh access token. BUT.. this also is not reliable, because user might have already authenticated using some other application.
To add more context to the problem... one of my SPA applications is a registration wizard, which uses REST API to register user and receives login_hint in return. This login_hint can be used as a wildcard in SSO redirect to pass the challenge and log in immediately.
However, if some other user is already logged in, we get in return session of this other user, not the one that should be logged in because of used login_hint.
We have discovered that our users very often for the first time visits our web application by browsing the direct URL of the OIDC client (https://oidcclienturl.com/), The ASP.NET Core OIDC authentication middleware kicks in and the user gets redirected back to Identityserver 4 login page.
Everything works fine but then they decide to add the (temporary? state, nonce, cookies...) authorization URL as a bookmark in their browser before entering their credentials and continuing back to the web application.
This causes an issue when the user later uses the bookmark in a new session. The login seem to actually work after entering valid user credentials even if the user uses an old authorization URL, but when the user gets redirected back to the web application they end up on a blank page (https://oidcclienturl.com/signin-oidc).
After the blank page have been loaded the user is able to browse the direct URL (https://oidcclienturl.com/) sucessfully and appear as an authentcated user in the web application.
Any ideas whats causing the blank page?
That blank page shouldnt exist, if I understand it correctly its the default callback path of the oidc authentication middleware in ASP.NET Core.
Unfortunately, the real-world problem of users bookmarking the login page isn't handled cleanly by OIDC, which requires the client app to initiate the login flow.
I've addressed this by adding a RegistrationClientId column to my user data table, which is the Identity Server ClientId corresponding to the client app that called IDS when the user account was created. In the client app configuration, we use the custom Properties dictionary to add a URI fragment:
new Client
{
ClientId = "some_client",
ClientName = "Some Client",
ClientUri = "https://localhost:5000",
Properties = new Dictionary<string, string>
{
{ "StartLoginFragment", "/Auth/StartLogin" }
}
// other config omitted
};
When a user logs in, an empty return URL indicates IDS wasn't called by a client app, so we use RegistrationClientId to query IClientStore, then we combine the ClientUri and StartLoginFragment URIs and use the resulting URI to redirect the user back to the client application.
Over in the client application, that endpoint kicks off the OIDC sign-in flow, and since the user is already signed-in on IDS, it comes right back to the correct location in the client app. The controller action looks like this:
[HttpGet]
public async Task StartLogin()
{
await acctsvc.SignOutAsync();
await HttpContext.ChallengeAsync("oidc",
new AuthenticationProperties()
{
RedirectUri = "/"
});
}
The call to SignOutAsync just ensures any client-app signin cookies are cleaned up. It's in our custom account service, but it just runs HttpContext.SignOutAsync on the usual "Cookies" and "oidc" schemes. Normally that would also result in a signout call to IDS, but the redirection by the subsequent ChallengeAsync replaces the pending signout call.
The downside is that the action is an HTTP GET meaning pretty much anyone could theoretically trigger this action. At most it would be an annoyance.
In the special case where your IDS is only handling auth for a single client, you can skip a lot of that -- if they land on the page with no return URL, just send them to your client app start-login endpoint straightaway, before they login.
I'm having problems integrating a CakePHP app (jSlate) into a bespoke non-Cake web application. All the alternative authentication scripts I've seen simply change the behaviour of the login form, in other words the login form still appears, and asks for username and password, but these are authenticated against an alternative source, such as LDAP.
What I actually want is for no login screen to appear at all. Instead I want a very simple behaviour:
Detect if user is already logged in to third party app.
If yes, automatically log them in to the CakePHP app (in this case jSlate).
If no, redirect to the third party app login screen.
Is there a tutorial for a CakePHP authentication along these lines? Or does someone know how to do this? I've worked out how to do part 3, but this behaviour is kind of useless without parts 1 and 2...
You can put this into your AppController::beforeFilter:
public function beforeFilter() {
if (!$this->Auth->user()) {
// if no user is currently logged in
if ($this->Cookie->read(...)) {
// or
if ($_COOKIE[...]) {
// or whatever else you want to detect
$this->redirect('http://some.external/login/service');
}
}
}
This external login service would then presumably redirect the user back to your Cake app at some point with some sort of token. You just need to define a publicly accessible action (no auth required) which it can redirect back to. In that action, you check all the tokens you need and can then "manually" authenticate the user:
$user = $this->User->find(/* find your Cake user by some id */);
if ($user) {
$this->Auth->login($user['User']['id']);
}
Congratulations, the user is now logged in as if he'd used a login form and has a valid Cake session.
i am using SimpleAuth by Alex (https://github.com/crhym3/simpleauth) in my GAE application. I have a Jquery Powered Login box in my base template which means users can login from any url inside the application. I want the users to be redirected back to the page they requested to login from. Is there any way we can achieve this with Oauth2 or can we just redirect users back to only a specific url ??
If you're using SimpleAuth I assume you're probably with webapp2, so #jmort253 examples are not exactly how I would do it (e.g. webapp2 has built-in sessions, so why use yet another library for sessions handling).
Though, conceptually it is correct: what you need is store original URL somewhere in a session before starting authentication process. Then use that stored URL for a final redirect, after successful authentication.
Starting from the example app code of SimpleAuth, what you basically need to change is the last line of _on_signin() to redirect users to that original URL they came from (instead of '/profile').
To store the original request URL you could use a simple wrapper, e.g.
def simpleauth_login_required(handler_method):
"""A decorator to require that a user be logged in to access a handler.
To use it, decorate your get() method like this:
#simpleauth_login_required
def get(self):
user = self.current_user
self.response.out.write('Hello, ' + user.name())
"""
def check_login(self, *args, **kwargs):
if self.request.method != 'GET':
self.abort(400, detail='The login_required decorator '
'can only be used for GET requests.')
if self.logged_in:
handler_method(self, *args, **kwargs)
else:
self.session['original_url'] = self.request.url
self.redirect('/my-login-page-where-users-can-choose-auth-method')
return check_login
Now, going back to that _on_signin() redirect line, instead of self.redirect('/profile') you'd do something like this:
target = self.session['original_url']
self.redirect(target)
A couple notes:
the example above assumes you have a logged_in method which indicates whether the current request is made by an already authenticated user or not;
you'll probably want to clear 'original_url' from the session (if they successfully authenticated)
The above example's credits go to webapp2_extras.appengine.users module.
When your user attempts to log in, your app first requests an access token then builds the Google OAuth2 URL. Your app redirects the user to google.com where your user must login. This login URL contains the access token from your server's request to Google. It's tied to that redirect URL for security purposes.
This redirect URL is designed to finalize the login process by returning a successful login operation to your app so that you can then finish logging in the user, registering that user, or doing whatever it is that your app would need to do.
After that, the ball is in your court, and you can program your app to do whatever you want. In other words, Google just cares about getting the request to your secure endpoint. Once that's done, Google no longer cares what you do; it's your app.
Therefore, to redirect your user back to the page he/she was on before logging in, which is an awesome usability improvement, is to do the following:
Steps to Redirect user:
1 Before the user logs in, store the page he/she is on in a session.
session = appengine_utilities.sessions.Session()
session["target_url"] = "/example_page.html" # sets keyname to current page
2 Next, go ahead and log the user in as normal.
3 After Google redirects the user to your authentication redirect URL, retrieve the target URL from the session:
target_url = session['target_url']
4 Lastly, redirect the user to this URL.
In short, you can accomplish the goal of redirecting your user to any target URL, so long as you first accept the redirect from Google to a single, established redirect URL.
The session examples came from the Beaker website. Additionally, Nick Johnson's Webapps on Appengine Part 5 Blog article dives into sessions with Beaker.
I would like to require the user to use a login/password when accessing some parts of my GAE (Google App Engine) application. I want to limit the access to the set of predefined users, e.g. on the page, where new data can be uploaded to the Datastore.
I found there are 3 ways on GAE how this can be solved, but it looks like only the last one (the most complicated) could be a solution for my issue:
Use a deployment descriptor (<security-constraint> in web.xml). This is not nice, since those users have access, even as viewers to the GAE application's admin page, where they can see the billing history, browse Datastore, logs, etc. and this is something I have to avoid.
Use the federated login (Open ID) (https://developers.google.com/appengine/articles/openid) and allow users to use their Google, Yahoo! or other identities to login. It is not a solution, since I want to limit the access to a small set of users (max. 5) and not allow everybody to use the application.
The other option is to create simple custom login page accessible only via HTTPS and let the user send the username & password in a POST request (it can be plain since we have secure https connection) to a servlet, on the servlet generate some session identifier with specified validity and make it part of every subsequent request. It also needs to check each time the user sends a GET or POST request if the session identifier exists and does not expire meanwhile manually.
Any other/better proposals on how to maintain administrator accounts? Can HttpSession help with the last variant?
Kind Regards,
STeN
I suggest the standard Google login page. Use something like this in you authentication controller (Java + Jersey Framework which is of course not necessary):
#Path("/login")
public Response login(#Context UriInfo ui) throws Exception {
UserService userService = UserServiceFactory.getUserService();
com.google.appengine.api.users.User user = userService.getCurrentUser();
Response response;
if (user == null) {
URI uri = new URI(userService.createLoginURL(ui.getBaseUri().toString()));
response = Response.seeOther(uri).build();
} else {
URI uri = new URI("/");
response = Response.seeOther(uri).build();
}
return response;
}
#GET
#Path("/logout")
public Response logout(#Context UriInfo ui) throws Exception {
UserService userService = UserServiceFactory.getUserService();
com.google.appengine.api.users.User user = userService.getCurrentUser();
Response response;
if (user == null) {
URI uri = new URI("/");
response = Response.seeOther(uri).build();
} else {
URI uri = new URI(userService.createLogoutURL(ui.getBaseUri().toString()));
response = Response.seeOther(uri).build();
}
return response;
}
The login method redirects you app to the Google login page if the user is missin (essentially not logged in). The logout method will logout the user.
Hope this helps
I use a combination of 2 and 3. I allow all users to login, but then I limit actions to particular e-mail addresses. These could be hard coded or (better) in the datastore and memcache (so that you don't have to query the datastore on every request). You could also cache this data in static variables in Java if you want to - just be aware that you might need to kill instances off manually if you change the users who have access. If, like me, you rarely / never change access then this shouldn't be a problem.
Allowing all users to login really gives them no access in my app - they see the admin pages but they're empty apart from a message saying "You do not have access to any of these administrative options".
Some notes:
AFAIK your assumption is not correct. Login to application has nothing to do with admin pages permission. You need to explicitly add users via "Permissions" page for them to have access to admin pages.
You can still check user properties (email) after user logs in with OpenID and deny them access.
This is of course doable. The natural way to track users are sessions. Google for examples.
In both cases 2. ' 3. it's advisable to have a servlet filter that checks session if there is user logged in and deny access (return 404) if user is not logged in.
Pay attention to the 3rd solution: instead of passing username & password my webapp asks username & apiSecret (generated automatically at the first login), so you can invalidate (and re-generate) quickly the apiSecret if something goes wrong.
There is another option: OAuth (https://developers.google.com/appengine/docs/java/oauth/).
Here is my piece of code (UserAccount is my class to represent a User; User is "com.google.appengine.api.users.User", retrieveUser(..) is the function to retrieve my UserAccount from a logged "User"):
public UserAccount getUserLogged(HttpServletRequest request) {
try {
User loggedUser = oauthService.getCurrentUser();
if(loggedUser!=null) {
return super.userAB.retrieveUser(loggedUser);
}
} catch (OAuthRequestException e) {
return null;
}
return null;
}