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;
}
Related
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 had a requirement that the user could enter his password and username directly on the client to sign on.
Without much trouble i just created a very simple extra authenticate action within the same application as identity server that looks like the following.
public async Task<IActionResult> AuthenticateUser(
[FromBody] LoginInputModel model,
[FromServices] AzureB2CUserService userService
)
{
var context = new ResourceOwnerPasswordValidationContext { Password = model.Password, UserName = model.Username };
await userService.ValidateAsync(context);
if (context.Result.Subject != null)
{
AuthenticationProperties props = null;
// only set explicit expiration here if persistent.
// otherwise we reply upon expiration configured in cookie middleware.
if (AccountOptions.AllowRememberLogin && model.RememberLogin)
{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
};
// issue authentication cookie with subject ID and username
// var user = _users.FindByUsername(model.Username);
await HttpContext.SignInAsync(context.Result.Subject.GetSubjectId(), model.Username, props, context.Result.Subject.Claims.ToArray());
return Ok(context.Result);
}
ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage);
return BadRequest(ModelState);
}
from the single page application I then call this action and if success, i know that a local authentication cookie has been for identity server.
Then i do a .userManager.signinSilent() from the oidc-client and since the cookie is there, it will get a token exactly the same way if I had used an implicit grant with userManager.signInRedirect but without the user getting redirected.
Is there something I should be aware of from a security point here. (You may assume that cross site attacks and antiforgery tokens have been handled).
instead of callign the silent signin after, could I just do a redirect to the implicit flow in the custom authenticate method and have it end up again with the client application ?
Is there something I should be aware of from a security point here.
(You may assume that cross site attacks and antiforgery tokens have
been handled).
My understanding is that you (1) forfeit the ability to participate in Single Sign-on since your browser does not redirect to the SSO Authority, and (2) introduce a weakness in password handling since your client app (both JS/C# in this case) see the password in the plain text.
instead of callign the silent signin after, could I just do a redirect
to the implicit flow in the custom authenticate method and have it end
up again with the client application ?
If you did this, then you would essentially have the authorization_code flow without the authorization code. Might as well just upgrade to the higher security of that flow.
Assuming you have a SPA on top of an ASP.NET MVC app, you could use a traditional MVC form post with a redirect to SSO and then, upon return, spin up the SPA.
I have a problem I can't figure out, in my application I have two login screens, one for the admin the other for the regular user. These login screens however point to the same controller in the same Auth namespace aside from that I have separated the rest of my controllers and routes into two namespaces; FrontEnd Namespace and BackEnd namespace.
Now my issue right now is when a regular user uses their credentials to login from the backend they are allowed access, I installed this package kodein/acl to handle roles and permissions and it seems to be working because when a user logs in to the backend now they can't do anything. The problem now is that even though they can't do anything they are still able to redirect to admin dashboard.
What I want here is this; when a regular user tries to login to admin backend they are denied access. I am a bit confused, i dunno how to o about it.
Do I have to create separate auth controllers and methods in both namespaces? Is that even possible? How would i go about it?
I use AngularJS for my frontend so in my route file i have this:
Route::group(['domain' => 'admin.website.loc'], function() {
Route::any('{url?}', function($url) {
return view('backend.index');
})->where(['url' => '[-a-zA-Z0-9/]+']);
});
Route::any('{url?}', function($url) {
return view('frontend.index');
})->where(['url' => '[-a-zA-Z0-9/]+']);
Which catch all urls and return to a single Index page,on the front end since i use JWT for authentication it validates the token on the frontend and if invalid or not available takes the user to the login page.
I thought of something else, maybe a temporary measure maybe permanent, i added the following code to my login function():
$url_parts = parse_url($request->url());
$host_parts = explode('.', $url_parts['host']);
if ($host_parts[0] == "admin")
{
$user = User::find(Auth::user()->id);
if (!$user->is('administrator'))
{
Auth::logout();
return response()->json(['error' => 'You Are Not Authorized!']);
}
}
first i get the request url,
then i get the "host" part of the request url and split it using '.' as the delimiter, this way i can check the subdomain.
Since my admin side uses a sub domain i check to see if the login request was from a url with the 'admin' subdomain, if it was then i check the authenticated user's role to see if they're administrator, if they are not, i log them out immediately and return an error message.
This way i don't have to create a separate function and route for the two login screens.
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.
I set up session handling on a google app project. This is supposed to allow my users to login and maintain state across pages. I just dumped it into the default greeting service to try it out:
public class GreetingServiceImpl extends RemoteServiceServlet implements GreetingService {
public void sessionTest(String username) {
HttpSession session = getThreadLocalRequest().getSession(false);
session.setAttribute("username", username);
}
}
then attempting to pull it out in my landing project.jsp page:
<%
String username = null;
HttpSession mysession = request.getSession(false);
if (mysession.getAttribute("username") != null) {
username = (String)mysession.getAttribute("username");
}
else {
username = "(not logged in yet)";
}
<p>You are:
<%= username %>
</p>
%>
It works, but I don't know how to send the data in sessionTest() securely. If I were sending the user's password in there too, it would be in the clear.
This would be ok (I think) if I was using https, but google app engine does not allow you to use https under custom domains (like www.mysite.com), they have to be under the (mysite.appspot.com) domain.
I'm kind of stuck here - how do we send passwords securely? If I was using php, I think I could use digest authentication (I'm not too experienced here) - can we do something like that with gwt + gae?
Thanks
Session data is stored on the server, not on the client - only an opaque token is sent to the client, to identify the client's session.
That said, you probably shouldn't store the user's password in the session - why would you want to? - or, indeed, in the clear at all.