How to deal with anonymous login attempts when you allow both anonymous access and logged in users? - scala

I have a framework that allows anonymous access as well as named user accounts. I'm exposing an OData resource on a certain URL. When configured as such, anonymous users can see parts of the resource and logged in users (through basic authentication) can see more.
The problem I'm facing is that some OData clients (like Excel) will initially attempt to access the OData resource anonymously even when you do provide credentials. Only when this fails, they will use the provided credentials. My understanding is that this is because there are many ways to log in and some clients just always try the most basic option first. But this prevents them from ever actually seeing more data, because they never use the provided credentials and also never get the authentication challenge when the resource allows anonymous access.
Is there a way to solve this issue, allowing both anonymous access AND properly sending an authentication challenge when possible? Is there maybe some header that clients will send when they do have credentials but just aren't supplying them initially?
Some (scala) code to make this a bit more tangible:
val (username, password) = getAuthInfo(request)
if (username != null && password != null) {
val regularSession = integration.core.login(username, password)
logger.debug(s"Login OK: User '$username' (Number of concurrent sessions: ${integration.core.getNumberConcurrentSessions}).")
(IMxRuntimeResponse.OK, null, regularSession)
} else if (integration.configuration.getEnableGuestLogin) {
val guestSession = integration.core.initializeGuestSession
logger.debug(s"Anonymous user '${guestSession.getUser.getName}' created " +
"(Number of concurrent sessions: ${integration.core.getNumberConcurrentSessions}).")
(IMxRuntimeResponse.OK, null, guestSession)
} else {
val responseMessage = "No basic authentication in header."
logger.warn(s"Login failed: $responseMessage")
(IMxRuntimeResponse.UNAUTHORIZED, responseMessage, null)
}
Somewhere else outside the surrounding try/catch:
if (httpStatusCode == IMxRuntimeResponse.UNAUTHORIZED)
response.addHeader("WWW-Authenticate", "Basic")
As you can see the challenge is never sent when anonymous access is allowed.
Edit: we investigated and there does not seem to be anything special in the headers of this request that would indicate this is an initial attempt that will result in another request when an authentication challenge is sent, rather than just another anonymous login attempt. We are at a loss here now on how to proceed.

Related

Is it "secure" to store a password and username in a .env file in a server to validate an admin API endpoint against?

Context
I've build a RESTful API server in Actix-Web with Rust that's hosted on a Heroku paid plan. It has n amount of publicly available endpoints to access content, alongside 3 strictly admin-only endpoints (for creating, editing, and deleting public content).
I am the only developer who'd ever need to access the admin-only endpoints - and infrequently at that. Several random users will be using the publicly available endpoints daily.
Normally, I'd implement an authentication/authorization strategy akin to this using JWTs (but obviously in Rust for my case). However, the added complexity that comes with this "more common" solution seems overkill for my simple use-case.
My theorized solution
Could I add a username and password field to the .env file in my project like so in order to match against a username and password passed in the admin-only handler functions?
... OTHER KEYS ...
USERNAME = my_really_long_random_username
PASSWORD = my_really_long_random_password
At first glance I'm storing passwords in plain text... but, there's only 1 and it's in my .env file, which is private by default.
All I'd do for the admin-only routes then is this (pseudo-code):
pub fn router_handler(passed_data) -> HttpResponse {
if passed_data.username == env.username && passed_data.password == env.password {
// CONSIDER THEM ADMIN
} else {
// BLOCK THEM AS THEY'RE NOT AUTHENTICATED
}
}
What I've tried
I have yet to try this strategy, but I'm curious about your opinions on it.
Question
Is my theorized solution secure? Does it seem reasonable given my use-case?
Response to question: jthulhu - is this what I do?
So, my .env file should look something like this:
... OTHER KEYS ...
USERNAME = a98ysnrn938qwyanr9c8yQden
PASSWORD = aosdf83h282huciquhr8291h91
where both of those hashes are the results of running my pre-determined username and password through my to_hash function which I added below (likely using a lib like this).
Then, my handler should be like this (psuedo-code):
pub fn router_handler(passed_data) -> HttpResponse {
if to_hash(passed_data.username) == env.username && to_hash(passed_data.password) == env.password {
// CONSIDER THEM ADMIN
} else {
// BLOCK THEM AS THEY'RE NOT AUTHENTICATED
}
}
You should never store passwords in plain text in a server, because if someones breaks in your server, and can read that file, they now have access to everything (whereas they might previously not). Not only that, but most people tend to reuse passwords, so storing one password in plain text means exposing several services where that password is used.
Instead, you should hash the passwords and store the hash. To perform a login, check if the hash of the given password corresponds to the one stored. This mechanism can be used with files or with databases alike, and is pretty much independent on how you actually store the hashes.

Getting error while accessing rally using rally-rest-api-2.1.2.jar

I am getting an authentication error for API key in rally. Even api key is given full access.
java.io.IOException: HTTP/1.1 401 Full authentication is required to access this resource
at com.rallydev.rest.client.HttpClient.executeRequest(HttpClient.java:163)
at com.rallydev.rest.client.HttpClient.doRequest(HttpClient.java:145)
at com.rallydev.rest.client.ApiKeyClient.doRequest(ApiKeyClient.java:37)
at com.rallydev.rest.client.HttpClient.doGet(HttpClient.java:221)
at com.rallydev.rest.RallyRestApi.query(RallyRestApi.java:168)
This is The code :
String wsapiVersion = "v2.0";
restApi.setWsapiVersion(wsapiVersion);
restApi.setApplicationName(projectname);
QueryRequest testCaseRequest = new QueryRequest("Testsets");
if(null !=workspace && ""!=workspace)
testCaseRequest.setWorkspace(workspace);
QueryResponse testCaseQueryResponse = restApi.query(testCaseRequest);
What is wrong here ?
One of the things I would check for is whether you are inside a corporate network that uses authenticated proxy servers. Unless you configure the connection correctly, the proxy will reject your request before it even gets to Rally.
Second thing I just thought of is, whether you are setting the right field in the header to enable the use of an APIKey. The Rally servers expect the ZSESSIONID to be set to the APIKey, I believe.

Meteor - Password recovery / Email confirmation dynamic url

Basically, I'm using the accounts-base package on meteor and on meteor startup, I set up what template the server should use for the password recovery mail, email confirmation mail, etc.
For example, in my server/startup.js on meteor startup I do many things like :
Accounts.urls.verifyEmail = function (token) {
return Meteor.absoluteUrl(`verify-email/${token}`);
};
Accounts.emailTemplates.verifyEmail.html = function (user, url) {
return EmailService.render.email_verification(user, url);
};
The problem is that my app is hosted on multiple host names like company1.domain.com, company2.domain.com, company3.domain.com and if a client wants to reset his password from company1.domain.com, the recovery url provided should be company1.domain.com/recovery.
If another client tried to connect on company2.domain.com, then the recovery url should be company2.domain.com.
From my understanding, this is not really achievable because the method used by the Accounts Package is "Meteor.absoluteUrl()", which returns the server ROOT_URL variable (a single one for the server).
On the client-side, I do many things based on the window.location.href but I cannot seem, when trying to reset a password or when trying to confirm an email address, to send this url to the server.
I'm trying to find a way to dynamically generate the url depending on the host where the client is making the request from, but since the url is generated server-side, I cannot find an elegent way to do so. I'm thinking I could probably call a meteor server method right before trying to reset a password or create an account and dynamically set the ROOT_URL variable there, but that seems unsafe and risky because two people could easily try to reset in the same timeframe and potentially screw things up, or people could abuse it.
Isn't there any way to tell the server, from the client side, that the URL I want generated for the current email has to be the client current's location ? I would love to be able to override some functions from the account-base meteor package and achieve something like :
Accounts.urls.verifyEmail = function (token, clientHost) {
return `${clientHost}/verify-email/${token}`;
};
Accounts.emailTemplates.verifyEmail.html = function (user, url) {
return EmailService.render.email_verification(user, url);
};
But I'm not sure if that's possible, I don't have any real experience when it comes to overriding "behind the scene" functionalities from base packages, I like everything about what is happening EXCEPT that the url generated is always the same.
Okay so I managed to find a way to achieve what I was looking for, it's a bit hack-ish, but hey..
Basically, useraccounts has a feature where any hidden input in the register at-form will be added to the user profile. So I add an hidden field to store the user current location.
AccountsTemplates.addField({
_id: 'signup_location',
type: 'hidden',
});
When the template is rendered, I fill in this hidden input with jQuery.
Template.Register.onRendered(() => {
this.$('#at-field-signup_location').val(window.location.href);
});
And then, when I'm actually sending the emailVerification email, I can look up this value if it is available.
Accounts.urls.verifyEmail = function (token) {
return Meteor.absoluteUrl(`verify-email/${token}`);
};
Accounts.emailTemplates.verifyEmail.html = function (user, url) {
const signupLocation = user.profile.signup_location;
if (signupLocation) {
let newUrl = url.substring(url.indexOf('verify-email'));
newUrl = `${signupLocation}/${newUrl}`;
return EmailService.render.email_verification(user, newUrl);
}
return EmailService.render.email_verification(user, url);
};
So this fixes it for the signUp flow, I may use the a similar concept for resetPassword and resendVerificationUrl since the signupLocation is now in the user profile.
You should probably keep an array of every subdomains in your settings and keep the id of the corresponding one in the user profile, so if your domain changes in the future then the reference will still valid and consistent.

How to implement user login in Jersey API

I am implementing RESTful API using javax.ws.rs. The response of the calls that I need to implement requires knowing which user is logged in currently. I tried making a separate call for the user login:
api.myapi.co.uk/authenticate?username=xxxx&password=xxxx
where I basically save the user information is a global variable
and then tried to make another call to retrieve information from the database based on the user that has been saved earlier but I find the value as null during the second call. What am I missing? Is my approach wrong? do you have any suggestions please?
Your code probably looks like this, right?
#Path("/")
public class MyResource{
private String username;
private String password;
#Path("authenticate")
public String authenticate(#QueryParam("username") username, #QueryParam("password") password) {
if(!username.equals("zainab") || !password.equals("letmein"))
return "Incorrect username or password";
this.username=username;
this.password=password;
return "Sucessfully authenticated";
}
#Path("secret")
public String secret() {
if(username == null || password == null)
return "Sorry, not authorized";
return "You are authorized: "+username;
}
}
If so, the problem is that JAX-RS creates a new Resource object for each request. A request to "/secret" then uses a new instance of MyResource, which has username and password as null.
Then, you might think, I'll just make it static! Then, your resource can't handle concurrent requests. What if Person A calls "/authenticate", then "/secret". Then Person B calls "/secret" without authenticating. He can then access "/secret" without authenticating!
Anyways, this violates the idea of RESTful services. The S in RESTful stands for "Stateless". This means that the server should store no state per client, and possibly give the user a token to pass with concurrent requests.
One possibility is to accept the username and password for every request to secret ("/secret?username=zainab&password=letmein"). Or you could implement token-based authentication, where the user calls "/authenticate" to get a token, and they pass that token on all later requests. Here is a link to help with that.
Also, note that username and password is usually not send in the URL as a query param, but instead in the Authorization HTTP header as such Authorization: base64encode("USERNAME:PASSWORD")

Firebase verifyIdToken + NodeJS Express Authentication design

Problem:
Due to legislation I have to store personal information within the EU (Social security number). Therefore I can't store this information in Firebase since there is no guarantee of geographical datacenter location when using Google's cloud services.
My proposed solution:
Having a Redis key value store with the sensitive information that can be accessed via a simple REST api where user authentication would be achieved using the users ID token, sent via HTTP Headers.
Firebase allows for verification of a user via the verifyIdToken method in the NodeJS library. This would allow me to check if the user ID matches any user id in my /admin end point of my Firebase. (Or I could hardcode the userIDs that would be allowed into the server since there aren't that many.)
So, the flow of the request would be as follows:
User signs in client side using the Firebase SDK.
Whenever the user needs access to the sensitive information it first gets the user's ID token
let currentUser = FIRAuth.auth()?.currentUser
currentUser?.getTokenForcingRefresh(true) {idToken, error in
if let error = error {
return
}
let headers = [
"X-FBUser-Token":idToken
]
//build request here to https://myServer.com/myEndpoint
}
Then server side we would retrieve the request
app.get('/myEndpoint', function(req, res) {
let idToken = req.get('X-FBUser-Token')
verifyToken(idToken, function(isAdmin){
if (isAdmin) {
//Fetch the key value pair and send it back to the client here
}
})
})
function verifyToken(idToken, cb) {
firebase.auth().verifyIdToken(idToken).then(function(decodedToken) {
var uid = decodedToken.sub;
firebase.database().ref('admins/' + uid).on('value', function (snap){
cb(snap.val() !== null)
})
}).catch(function(error) {
// Handle error
});
}
And then the client would receive back the response and deal with it. Everything done over HTTPS ofcourse.
Note: I know that the code above is rather crude and would need some refinement, but hopefully you get the concept
My questions:
First of all, is this a safe way of doing things?
Is there a better, more straight forward approach?