Worklight App submits lots of requests which result into HTTP 401 - ibm-mobilefirst

We have worklight app with app security defined in application-descriptor.xml. We have challenge handler to handle the challenges. In wlCommonInit() function, we call WL.Client.Connect() function which in turns triggers the challenge handler. User can type in user id / password and authenticate successfully. All good till this point.
In challenge handler after successful authenticate we call ChallengeHandler.submitSuccess() method to inform worklight about successfull authentication.
This call should result into WL.client.connect() onSuccess callback function, but instead it makes lot of request to URL ../App/iphone/init and retuns with 401. Eventually after 1-2 minutes it gets HTTP 200 for particular request and then enters into onSuccess().
Any idea why so many requests which result into 401?
Below is code snippet, in main.js...
WL.Client.connect({
onSuccess : callbackOnSuccess,
onFailure : callbackOnFailure
});
in challengeHandler.js..
$('#loginButton').bind('click', function () {
var reqURL = '/j_security_check';
var options = {};
options.parameters = {
j_username : $('#username').val(),
j_password : $('#password').val()
};
options.headers = {};
ChallengeHandler.submitLoginForm(reqURL, options, ChallengeHandler.submitLoginFormCallback);
});
ChallengeHandler.submitLoginFormCallback = function(response) {
WASLTPARealmChallengeHandler.submitSuccess();
};

Theory:
Do you have a single MobileFirst Server or multiple?
If you have only one server, it would be helpful to get network traffic log from a tool such as Wireshark
If you multiple servers, do you then also happen to have a Load Balancer involved?
In order for authentication to successfully pass there will be several requests - the first for triggering the challenge handler and the second to carry the user credentials. These need to reach the same server.
In case the Load Balancer is misconfigured requests may hit different MobileFirst Servers. It does sound like the requests are getting bounced between servers then meaning that a authentication request hits one server but the credentials requests hits another...
So in the case of multiple servers you need to make sure that Sticky Sessions options is enabled in the used Load Balancer

Related

Cloudflare Worker redirect stripping auth headers

I set up a Cloudflare worker to redirect to our API gateway since we don't have control of the DNS and can't just set up a CNAME. The redirect works and it passes along the body and all the headers except Authorization. It receives it, and when I look at the worker console it lists it as redacted. It also redacts the user_key param I'm passing but it passes that through.
const base = 'https://myurl.com'
const statusCode = 308;
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const url = new URL(request.url);
const { pathname, search } = url;
const destinationURL = base + pathname + search;
return Response.redirect(destinationURL, statusCode);
}
First, note that the redactions you are seeing are purely for display in the workers console. This is a feature to protect sensitive secrets from being logged, but it doesn't affect the content of any live request.
Now, with regard to what your Worker is actually doing:
This worker returns a 308 redirect response back to the client. It is then up to the client to follow the redirect, sending the same request to the new URL.
It is the client, then, that decides whether to send the Authorization header to the new location -- the behavior is NOT controlled by Cloudflare Workers. As it turns out, many clients intentionally drop the Authorization header when following redirects to a different domain name. For example, the Go HTTP client library does this, and node-fetch recently started doing this as well. (I happen to disagree with this change, for reasons I explained in a comment.)
If the client is a web browser, then the behavior is complicated. If the Authorization header was added to the request as part of HTTP basic auth (i.e. the user was prompted by the browser for a username and password), then the header will be removed when following the redirect. However, if the Authorization header was provided by client-side JavaScript code when it called fetch(), then the header will be kept through the redirect.
Probably the best way to solve this is: Don't use a 3xx redirect. Instead, have the Worker directly forward the request to the new URL. That is, instead of this:
return Response.redirect(destinationURL, statusCode);
Try this:
return fetch(destinationURL, request);
With this code, the client will not receive a redirect. Instead, the Worker will directly forward the request to the new URL, and then forward the response back to the client. The Worker acts as a middleman proxy in this case. From the client's point of view, no forwarding took place, the original URL simply handled the request.

Can Cypress intercept requests being made directly to a server?

I have been trying to intercept a server request using Cypress' intercept method.
I have noticed that Cypress can intercept requests made through the front-end/browser, however, the intercept method doesn't work if I make a request directly to the back-end server.
Let me clarify what I mean:
One thing is intercepting a request that the front-end/browser makes to the back-end server.
Another thing is intercepting a call that doesn't use the browser but calls directly the back-end endpoint.
For example:
I can create a user using the front-end interface
or I can create a user calling the back-end endpoint directly (directly calling the server).
Coming back to my question. Is there a way to intercept a call that was made directly to the back-end endpoint?
This is what I have tried so far:
I wrote a regex to intercept api/v0/customers
I then made a request to http://locahost:5440/api/v0/customers (which is the URL of the server)
Finally, I waited for the request to happen
Timeout request using Cypress intercept method
cy.intercept(/^\/api\/v0\/customers\/$/).as('createCustomer');
cy.request(createCustomer(customerData, headers));
cy.wait('#createCustomer').then(({ status, body }) => {
const customerId = body.customer_id;
console.log(body);
expect(status).equal(201);
});
Here's the problem: There was a timeout error.
As you can see in the image, I'm making a request to http://locahost:5440 which is the server URL. NOTE: I made sure the server was up and running.
The regex is also correct and it will match the endpoint http://locahost:5440/api/v0/customers
I suspect that intercept only works for requests being made through the browser. Is this assertion correct? I couldn't find this answer anywhere in the Cypress docs.
Is there a way for me to intercept a call being made directly to a server (not using the browser)?
You don't have to intercept the requests you explicitly make with cypress, just use .then to get the response, like this:
cy.request(createCustomer(customerData, headers)).then((response) => {
const customerId = response.body.customer_id;
console.log(response.body);
expect(response.status).equal(201);
});
Reference: https://docs.cypress.io/api/commands/request#Yields

Websocket, Angular 2 and JSON Web token Authentication

My Angular 2 app (coded in typescript) has a simple authentication scheme:
User logs in:
Server returns JSON Web Token (JWT) abc123...
On every API call, the app sends the JWT in the Authorization header
Server validates the JWT and grants access
Now I'd like to add websockets. I'm wondering how to authenticate the user there. Since I don't control which headers are sent to the websocket server (WS), I cannot send the JWT.
My idea so far (not yet implemented):
Client opens websocket: let sock = new WebSocket('wss://example.com/channel/');
WS server accepts the handshake without any authentication check. Standard HTTP headers are available at this stage.
Client listens to the open event on the socket. Once the socket is open:
client sends a message with type='auth' payload='JWT_VALUE'
WS server expects 1st message on a socket to be of type auth. Once that is received, server reads the payload, validates JWT_VALUE and sets an isAuthenticated flag
If validation fails, server disconnects the socket
If a client without isAuthenticated sends any other type of message, server disconnects the socket
2 problems: server resources can be taken up by clients who connect but never send the JWT, and a cleaner solution would block the handshake if the client is not authenticated.
Other ideas:
Client could send JWT in the path: new WebSocket('wss://example.com/channel/<JWT>/')
pro: this info is available during the handshake
con: the path doesn't seem to be the "appropriate" place for a JWT. Specifically because intermediate proxies and access logs will save the path; When designing the HTTP API I already made the decision not to include the JWT in the url
Server could read the client's IP + UserAgent and match against a DB record that was created by the HTTP server when the JWT was issued. Server will then guess who is connecting
pro: this info may be available during the handshake (not sure about IP)
con: it seems horribly insecure to "guess" that a client should be associated with a JWT when the client never presented it in the first place. It would mean for instance that someone who spoofs the victim's UA and uses the same network (proxy, public wifi, university intranet...) will be able to impersonate the victim.
How do you authenticate clients on websockets? Assume the user already logged in via HTTP and that the Angular 2 app has a JWT token.
I settled on the following protocol:
1. Client logs into the site and receives an authentication token (JSON Web Token)
GET /auth
{
user: 'maggie',
pwd: 'secret'
}
// response
{ token: '4ad42f...' }
2. Authenticated client requests a websocket connection ticket
GET /ws_ticket
Authorization: Bearer 4ad42f...
// response: single-use ticket (will only pass validation once)
{ ticket: 'd76a55...', expires: 1475406042 }
3. Client opens the websocket, sending the ticket in query param
var socket = new WebSocket('wss://example.com/channel/?ticket=d76a55...');
4. Websocket server (PHP) then validates the ticket before accepting the handshake
/**
* Receives the URL used to connect to websocket. Return true to admit user,
* false to reject the connection
*/
function acceptConnection($url){
$params = parse_str(parse_url($url, PHP_URL_QUERY));
return validateTicket($params['ticket']);
}
/** Returns true if ticket is valid, never-used, and not expired. */
function validateTicket($ticket){/*...*/}
Use djangorestframework-jwt to generated your JWTs, and the following Django-Channels 2 middleware.
The token can be set via the djangorestframework-jwt http APIs, and it will also be sent for WebSocket connections if JWT_AUTH_COOKIE is defined.
settings.py
JWT_AUTH = {
'JWT_AUTH_COOKIE': 'JWT', # the cookie will also be sent on WebSocket connections
}
routing.py:
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path
from json_token_auth import JsonTokenAuthMiddlewareStack
from yourapp.consumers import SocketCostumer
application = ProtocolTypeRouter({
"websocket": JsonTokenAuthMiddlewareStack(
URLRouter([
path("socket/", SocketCostumer),
]),
),
})
json_token_auth.py
from http import cookies
from channels.auth import AuthMiddlewareStack
from django.contrib.auth.models import AnonymousUser
from django.db import close_old_connections
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
class JsonWebTokenAuthenticationFromScope(BaseJSONWebTokenAuthentication):
"""
Extracts the JWT from a channel scope (instead of an http request)
"""
def get_jwt_value(self, scope):
try:
cookie = next(x for x in scope['headers'] if x[0].decode('utf-8') == 'cookie')[1].decode('utf-8')
return cookies.SimpleCookie(cookie)['JWT'].value
except:
return None
class JsonTokenAuthMiddleware(BaseJSONWebTokenAuthentication):
"""
Token authorization middleware for Django Channels 2
"""
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
try:
# Close old database connections to prevent usage of timed out connections
close_old_connections()
user, jwt_value = JsonWebTokenAuthenticationFromScope().authenticate(scope)
scope['user'] = user
except:
scope['user'] = AnonymousUser()
return self.inner(scope)
def JsonTokenAuthMiddlewareStack(inner):
return JsonTokenAuthMiddleware(AuthMiddlewareStack(inner))
Client opens the websocket, sending the UserName and Password in query param
ws://<username>:<password>#<ip-address><path>
Example: new $WebSocket('ws://user:123456#127.0.0.0/util')

loopback protected routes/ensure login

How do I ensure that a user is logged in before I render a view using loopback?
I can loggin in the front end using my angular app. But I wanted to block anonymous users from viewing the page.
I thought it would be a header, something like headers.authorization_token, but it does not seem to be there.
I am looking for something like connect-ensurelogin for passport, without having to use passport.
This is the $interceptor that solves your problem.
This code detects 401 responses (user not logged in or the access token is expired) from Loopback REST server and redirect the user to the login page:
// Inside app config block
$httpProvider.interceptors.push(function($q, $location) {
return {
responseError: function(rejection) {
if (rejection.status == 401) {
$location.nextAfterLogin = $location.path();
$location.path('/login');
}
return $q.reject(rejection);
}
};
});
And this code will redirect to the requested page once the user is logged in
// In the Login controller
User.login($scope.credentials, function() {
var next = $location.nextAfterLogin || '/';
$location.nextAfterLogin = null;
$location.path(next);
});
Here is one possible approach that has worked for me (details may vary):
Design each of the Pages in your Single Page Angular App to make at one of your REST API calls when the Angular Route is resolved.
Secure all of your REST API Routes using the AccessToken/User/Role/ACL scheme that LoopBack provides.
When no valid Access Token is detected on the REST Server side, pass back a 401 Unauthorized Error.
On the Client Side Data Access, when you detect a 401 on your REST Call, redirect to your Logic Route.
For the smoothest User Experience, whenever you redirect to Login, store the Route the User wanted to access globally
(localStore, $RootScope, etc.) and redirect back there when the User
Logs in and gets a valid Access Token.
Here is the LoopBack Access Control sample: https://github.com/strongloop/loopback-example-access-control

JAX-RS, RestEasy: No session cookie

Folks,
Used to be that when you hit a servlet/jsp, the app server would automatically start a session. It would put a session cookie in the first dynamic response that would get tracked throughout.
I have a rest backend and I notice that no session cookies are being traded. So I manually add code to send the JSESSIONID cookie:
#Context
private HttpServletRequest httpRequest;
// ...
#GET
#Path( "/{rcpGuid}" )
public Response myMethod( ... )
{
final HttpSession session = httpRequest.getSession();
final String sSessionId = session.getId();
...
return Response.status( Response.Status.SEE_OTHER ).
location( redirectUrl ).cookie( new NewCookie( "JSESSIONID", sSessionId ) );
}
Now this is causing 2 copies of the JSESSIONID cookie being returned where before there was no Set-Cookie header. This is what I see now in my browser's inspector:
Set-Cookie:JSESSIONID=sdm-Q1P6pRoQbKd4-9cJylGb; Path=/nn, JSESSIONID=sdm-Q1P6pRoQbKd4-9cJylGb; Version=1
I don't care as long as this would work. But unfortunately, when my browser requests the URL being redirected to (notice that the response is "SEE_OTHER"), that request does not bear the session id. This causing my app to not function right.
Any insights?
Used to be that when you hit a servlet/jsp, the app server would automatically start a session. It would put a session cookie in the first dynamic response that would get tracked throughout.
The app server doesn't create a session until httpRequest.getSession() is called.
I have a rest backend and I notice that no session cookies are being traded. So I manually add code to send the JSESSIONID cookie:
In your example, you call getSession() and create a separate jsessionid cookie. That would explain why you have two cookies. If you do neither, you wont have a jessionid at all.
If it is a REST back-end, so you should not have JSESSIONID cookie and keep your back-end endpoints as Stateless.
All informations required to get a result from one of your web service should be contained in the request to the web service. A web service is idempotent.