Sending a password securely using gwt and app engine? - google-app-engine

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.

Related

ASP.NET 6 WebAPI Authentication with SSO

I have an ASP.NET 6.0 Web API project. I would like to add authentication and authorization to it, but it must use SSO via Azure.
We already have a SPA application that does this, it uses the Angular MSAL library to redirect the user to an SSO Login page, then returns to the SPA with an access token. The access token is then added to the header of each request to the Web API, which uses it to enforce authentication.
Now we want to share our web API with other teams within our organization, and we would like to have that login process just be another API call, rather than a web page.
Conceptually, a client would hit the /login endpoint of our API, passing in a userID and password. The web API would then get an access token from Azure, then return it as the payload of the login request. It's then up to the client to add that token to subsequent request headers.
I have done this with regular ASP.NET Identity, where all of the user and role data is stored in a SQL database, but since our organization uses SSO via Azure Active Directory, we would rather use that.
I have researched this topic online, and so far all of the examples I have seen use a separate SPA, just like we already have. But as this is a web api, not a front-end, we need to have an API method that does this instead.
Is this even possible? I know Microsoft would rather not have user credentials flow through our own web server, where a dishonest programmer might store them for later misuse. I understand that. But I'm not sure there's a way around this.
Thanks.
I believe you are looking for the Resource Owner Password (ROP) flow. You can use IdentityModel.OidcClient to implement it.
Sample code:
public class Program
{
static async Task Main()
{
// call this in your /login endpoint and return the access token to the client
var response = await RequestTokenAsync("bob", "bob");
if (!response.IsError)
{
var accessToken = response.AccessToken;
Console.WriteLine(accessToken);
}
}
static async Task<TokenResponse> RequestTokenAsync(string userName, string password)
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Constants.Authority);
if (disco.IsError) throw new Exception(disco.Error);
var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "roclient",
ClientSecret = "secret",
UserName = userName,
Password = password,
Scope = "resource1.scope1 resource2.scope1",
Parameters =
{
{ "acr_values", "tenant:custom_account_store1 foo bar quux" }
}
});
if (response.IsError) throw new Exception(response.Error);
return response;
}
}
Sample taken from IdentityServer4 repository where you can find more ROP flow client examples.
I would recommend that you don't go with this implementation and instead have all clients obtain their access tokens directly from Azure AD like you did with your Angular SPA.

Office 365 Access via Graph API

I have a problem (or two) with regards to accessing my office 365 account via the Microsoft Graph API.
The first issue is that I have a java program that is attempting to list all users in the office 365 subscription. I am calling https://graph.microsoft.com/v1.0/users/ but getting a 403 forbidden back.
On the App registration, I have added permissions including User.Read, User.ReadBasic.All, User.ReadWrite on both delegated and app permissions.
I have also tried to use the Graph Explorer, but when I enter to use my account it still uses the built in graph user and doesn't show my application login info. Not sure if these are related.
Here is code snippet that results in a 403
AuthenticationResult result = getAccessTokenFromUserCredentials(RESOURCE_GRAPH, ID, PASSWORD);
URL url = new URL("https://graph.microsoft.com/v1.0/users/") ;
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Authorization", "Bearer "+result.getAccessToken());
if (conn.getResponseCode() != 200) {
throw new RuntimeException("Failed : HTTP error code : "
+ conn.getResponseCode());
}
And here is the method that gets the token
private static AuthenticationResult getAccessTokenFromUserCredentials(String resource,
String username, String password) throws Exception {
AuthenticationContext context;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(AUTHORITY, false, service);
Future<AuthenticationResult> future = context.acquireToken(
resource, CLIENT_ID, username, password,
null);
result = future.get();
} finally {
service.shutdown();
}
if (result == null) {
throw new ServiceUnavailableException(
"authentication result was null");
}
return result;
}
The app register in apps.dev.microsoft.com works with the v2.0 endpoint .Please click here for more details about the v2.0 endpoint .
You can acquiring token using v2.0 authentication protocols and Azure Active Directory v2.0 authentication libraries . During authentication , you need to do user consent or admin consent for User.ReadBasic.All permission . After consenting , access token includes that delegate permission and will work when calling list users operation .
OK, thought I should post up the answer. Firstly, and most confusingly, the apps.dev.microsoft.com registration didn't seem to work (even though I was using the V2.0 endpoint and the version 2 libraries).
However, when I registered the app using the azure portal directly, this fixed the issue. I have subsequently been able to access the service correctly.
It seems strange that, although the authentication / authorisation service was standard for my app and worked perfectly for accessing Sharepoint / One Drive etc, but, when wanting to hit the users endpoint, it would only work if it was registered in the portal.azure.com.
Many thanks everyone for your help.

signin from external (but trusted) clients in identity server 4

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.

Authentication on Google App Engine (web page)

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;
}

Appengine user service +GWT not logging out

I have an appengine app with a GWT frontend. I am using appengine's user service to authenticate with google accounts. My problem is when logging out from the GWT frontend the user is not completely logged out. The user is shown the login page, however when you click to login again with google a google account, it goes straight to the app without going to the google login page. I am not using any custom login/pass fields here, strictly appengine user service.
I am guessing this has something to do with HTTP sessions and basic authentication, however I have not been able to log out entirely.
Here is the Login/out service on the server:
import javax.servlet.http.HttpSession;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
#SuppressWarnings("serial")
public class LoginServiceImpl extends RemoteServiceServlet implements
LoginService {
public final static String CHANNEL_ID = "channel_id";
#Override
public UserAccountDTO getLoggedInUserDTO() {
UserAccountDTO userDTO;
HttpSession session = getThreadLocalRequest().getSession();
UserAccount u = LoginHelper.getLoggedInUser(session, null);
if (u == null)
return null;
userDTO = UserAccount.toDTO(u);
UserService userService = UserServiceFactory.getUserService();
userDTO.setLogoutURL(userService.createLogoutURL(requestUri));
return userDTO;
}
#Override
public void logout() throws NotLoggedInException {
getThreadLocalRequest().getSession().invalidate();
throw new NotLoggedInException("Logged out");
}
}
On the GWT client side I am using this code to logout:
Window.Location.assign(currentUserDTO.getLogoutURL());
When I click the logout link on my app (which runs the code above), nothing changes. However If I reload the page I am sent to my app's login page. When I click to login with my google account it goes straight into my app without asking for google credentials. This is tells me the user was logged out from my appengine app, however the the user is still somehow logged in to his google account in the browser (I'm assuming an auth token stored as a cookie?). I need to have my users completely logged out of there google account so the next visitor to the site is asked for google credentials.
Ideally to LogOut from GAE/Google I would use logOutUrl coming out of userService. For example
UserService userService = UserServiceFactory.getUserService();
logOutURL = userService.createLogoutURL(baseURL);
logOutURL is where I would redirect window to, to log out from Google
Also check a small servlet I have written to login and logout at: http://cloudspring-demo.appspot.com/html/csAuth.html
You can simply copy this servlet in appropriate servlet and after adding mapping in web.xml, you can simply invoke it to test out.

Resources