Reactivesearch proxylike backend with cookie authentication - reactivesearch

I have a spring backend which i'm accessing my Elastic search cluster through by a proxylike endpoint. The request has to be authorized with a cookie.
I'm currently using searchkit with supports authenticating requests through the withCredentials flag. Is there a similar option for reactivesearch or is there any other solution for authorizing the request with a cookie?
I could add: the backend exposes a swagger client which runs on a different domain than my frontend client. This client "owns" the cookie and thus i cannot read the cookie from my frontend client

You can use the headers prop in ReactiveBase to pass custom headers along with the requests. Link to docs. Since there is no withCredentials you could read the cookies and set in custom headers to verify the requests at the proxy middleware.
<ReactiveBase
...
headers={{
customheader: 'abcxyz'
}}
>
<Component1 .. />
<Component2 .. />
</ReactiveBase>
Here is a sample proxy server but its in NodeJS

Okey so it turns out, Reactivesearch uses fetch and fetch wants credentials: 'include' for cookie authentication. This may not be placed in the headers that Reactivesearch supplies and must be placed on the root object for the request.
It's possible to do this by implementing beforeSend on ReactiveBase.
const Base = ({ children }) => {
const onBeforeSend = props => {
return {
...props,
credentials: 'include',
}
}
return (
<ReactiveBase
app="app-name"
url="url"
beforeSend={onBeforeSend}
>
{children}
</ReactiveBase>
)
}

Related

Pass Along Authorization Header in OpenAPI Generator-generated Java WebClient Code

I have a Java/Spring-based microservices architecture with two services:
A - has a public-facing endpoint which does some stuff and then calls the below endpoint on B. This endpoint requires an Authorization header (OAuth2) to identify the user.
B - has an endpoint that also requires an Authorization header (OAuth2) so that it can determine which user made the call.
I have specified B's endpoint using OpenAPI. I'm using OpenAPI Generator to generate both the client in A (Spring WebClient), and the server in B (Spring Boot).
My question is this: what do I need to do to pass the Authorization header along from A to B? I see how to set a static header, but I don't know how to pass the header based on what's received by A.
Similar to this question, but for WebClient: OpenAPI client generator Java - header per call
As your A service is a resource-server and you want to issue request to service B on behalf of the user who initiated the request to A, just set a Bearer Authorization header on WebClient with the original access-token string retrieved from current security context (use SecurityContextHolder static accessor or have AbstractOAuth2TokenAuthenticationToken<?> auth auto-magically injected by Spring as #Controller method parameter).
If your A service was a client, you could do as I did in the UiController of this tutorial.
Turns out my problem was how I specified the endpoint security in my OpenAPI specification.
I added:
components:
securitySchemes:
s2s:
type: oauth2
flows:
clientCredentials:
authorizationUrl: https://example.com/oauth/authorize
tokenUrl: https://example.com/oauth/token
scopes:
read: Read scope
And made a reference to that security schema on my endpoint:
/foo:
get:
...
security:
- s2s:
- read
Now, when I run openapi-generate on this schema and generate it to either Spring Boot (server) or Java WebClient (client), the generated endpoint signature looks like:
#RequestMapping(
method = RequestMethod.GET,
value = "/foo",
produces = { "application/json" }
)
Mono<ResponseEntity<MyResponse>> foo(
#Parameter(name = "Authorization", description = "", required = true) #RequestHeader(value = "Authorization", required = true) String authorization,
#Parameter(hidden = true) final ServerWebExchange exchange
);
The String authorization argument to the method was not previously being generated and it's what I needed here. It allows me to pass A's header along to the call to B.
Props to #Ch4mp for helping out here.

Keycloak Invalid token issuer

I have a mobile app(react-native), a resource service(spring boot) and Keycloak Authenticatioin Service(Auth-Service).
Client makes authentication directly with Auth-Service and gets the access token.
When I do a request to the resource service, the resource service checks the access token by asking to the Auth-Service. But token obtained by the client app and iss field is http://10.0.2.2:8060/auth/realms/sau and my resource service at http://localhost:8110.
Keycloak says: error="invalid_token", error_description="Invalid token issuer. Expected 'http://localhost:8060/auth/realms/sau', but was 'http://10.0.2.2:8060/auth/realms/sau'"
My question is how can I make authentication in resource service behalf my client?
Mobile App:
export const prepareRequestBody = credentials => {
const params = new URLSearchParams();
params.append('username', credentials.username);
params.append('password', credentials.password);
params.append('client_id', "nasilim-mobile-app");
params.append('grant_type', "password");
return params;
};
export const login = credentials => {
const params = prepareRequestBody(credentials);
return axios.post(LOGIN, params);
};
Resource-Service:
application.yml
keycloak:
realm: sau
resource: photo-service
bearer-only: false
auth-server-url: http://localhost:8060/auth
credentials:
secret: 69a3e80c-8360-42df-bcec-b6575a6949dc
Note: I have checked this question and I have tried to set "X-Forwarded-For" : "http://localhost:8060/" but It didn't work Keycloak returns:
{
"error": "invalid_request",
"error_description": "HTTPS required"
}
Here is a Sample Access Token that obtained by mobile client.
The "iss" claim vary in function of the request. The variable KEYCLOAK_FRONTEND_URL can change this behavior. So try do as follow in your docker-compose file:
KEYCLOAK_FRONTEND_URL: http://10.0.2.2:8060/auth
You need to configure access from your Spring Boot app to the Auth server in an external fashion, not localhost:
keycloak:
realm: sau
resource: photo-service
bearer-only: false
auth-server-url: http://10.0.2.2:8060/auth
credentials:
secret: 69a3e80c-8360-42df-bcec-b6575a6949dc
This way the token issuers will match. This will probably require either to disable SSL requirement for external request in keycloak or to configure proper SSL communication. If this is meant for production, do the right way.
See also:
https://stackoverflow.com/a/42504805/1199132
What if you can not access the auth server using the external address? This will not work. Check https://issues.redhat.com/browse/KEYCLOAK-6984
One workaround is to set the reaml public key. But it's not recommended as the adapter will not check for new key if the key is rotated.
Use the proxy-url in the adapter configuration to provide an alternative URL.
See docs.
Spring application.yml:
keycloak:
authServerUrl: http://10.0.2.2:8060/auth
proxyUrl: http://localhost:8060/auth
...
Or keycloak.json:
{
"auth-server-url": "http://10.0.2.2:8060/auth",
"proxy-url": "http://localhost:8060/auth",
...
}
At keycloak go to your realm then to realm settings and place the url you want at frontend url.
Problem is because inside "container" there is different ip(host).
Keycloak has property hostname-url. You should inform keycloak about your frontend(hosts). You can do that during starting new instance of keycloak. Command to start your keycloak is start-dev --hostname-url=url-to-your-frontend
you can provide the hostname of frontend, so the command will be look like this:
start-dev --hostname-url=http://10.0.2.2:8060
This solution is working for Keycloak version 19 and up

Paw not finding access_token from OAuth proxy

I have a use-case where I need to spoof a white-listed Redirect URL locally when performing OAuth 2 authentication.
I'm running a very basic web-server coupled with a hosts file entry for the domain I'm spoofing. I'm able to correctly negotiate my tokens and return them to Paw, but Paw isn't picking up my access_token or refresh_token, it simply displays the raw response:
Here's my server code (with placeholders for sensitive data):
var http = require('http'),
request = require('request');
var PORT = 6109;
var server = http.createServer(function(req, res) {
var code = req.url.split('?')[1].split('=')[2];
request({
url: 'https://<access token URL>/oauth2/token?code=' + code,
method: 'POST',
form: {
'client_id': <client_id>,
'client_secret': <client_secret>,
'grant_type': 'authorization_code',
'redirect_uri': <spoofed redirect URL>
}
}, function(err, response, data) {
data = JSON.parse(data);
res.writeHead(200, {'Content-Type': 'application/json'});
res.write(JSON.stringify(data.result));
// I also tried this with the same end-result
// res.writeHead(200);
// res.write('access_token=' + data.result.access_token + '&token_type=' + data.result.token_type + '&refresh_token=' + data.result.refresh_token);
res.end();
});
});
server.listen(PORT, function() {
console.log('Server listening on port %d', PORT);
});
What am I missing? Why isn't Paw finding my tokens?
Here's my configuration for reference:
Some other noteworthy points:
The OAuth provider is non-standard and flubs quite a few things from the spec (my proxy exists in part to patch up the non-standard bits)
The domain for the Redirect URL is real, but the URL does not resolve (this is a part of the reason for the local hosts entry)
I'm not showing this part of the flow, but I am correctly completing the authorization step prior to being given the code value
I think you're probably confused between the Authorization URL and Access Token URL. When you're in Authorization Code grant type for OAuth 2, you're expected to have a user confirmation step in a web page (the Authorization URL).
Which makes me guess that instead, you're expecting instead to use the Password Grant or Client Credentials? Otherwise, if you want to use Authorization URL, you'll need to specify a webpage at the Authorization URL.
Note: I've tried your Node.js script in Paw using the two last grants I mentioned (Password Grant & Client Credentials), and it works nicely.
Update: Following the comments below, I understand more what you are doing. The Authorization Request should (if successful) return a 302 redirect response to the Redirect URL page, and append a code URL query param to it. It seems like you're returning a JSON response with the code instead, so Paw isn't catching it.
According to the OAuth 2.0 spec (RFC 6749), section *4.1.2. Authorization Response*, if granted, the code should be passed as a URL query param (i.e. a ?key=value param in the URL) to the Redirect URL when doing the redirection.
If the resource owner grants the access request, the authorization
server issues an authorization code and delivers it to the client by
adding the following parameters to the query component of the
redirection URI using the "application/x-www-form-urlencoded" format
Quoting the example from the spec, here's how the response of the Authorization Request should look like if it's a success (code is granted):
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz
I saw that the Redirect URL contains "my Spoofed Uri".
When we need to use authorization code flow, we provide the authorization code and redirect Uri.
When the URI you are providing does not match the URI saved for the client in Identity server, you will not be able to get the token as the URI does not match with the client authorization code.
For example : Consider client identity in the Identity server be:
Auth Code: "xyx"
Redirect Uri: "www.mylocalhost.com\xyz"
And in your example the combination you are providing is:
Auth Code: "xyx"
Redirect Uri: "<my spoofed uri>"
As these 2 wont match there will be no token received.
I believe if you use the correct URI that is registered with the client in the Identity server, you will be able to receive the token.

Request option "withCredentials" - Angular2

I am getting a CORS issue using request to connect to Neo4j in an Angular2 component:
Response to preflight request doesn't pass access control check. A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true. Origin 'http://localhost:8080' is therefore not allowed access. The credentials mode of an XMLHttpRequest is controlled by the withCredentials attribute
How do I set withCredentials: false using request (Typescript)? I'm assuming this will fix the problem. But the request ts file does not list withCredentials in its CoreOptions object. Neither does the Neo4j-Typescript package include this in its Typescript definition.
You can do this by extending the BrowserXhr class:
#Injectable()
export class CustomBrowserXhr extends BrowserXhr {
constructor() {}
build(): any {
let xhr = super.build();
xhr.withCredentials = false;
return <any>(xhr);
}
}
and override the BrowserXhr provider with the extended:
bootstrap(AppComponent, [
HTTP_PROVIDERS,
provide(BrowserXhr, { useClass: CustomBrowserXhr })
]);
I had the same issue in my Angular2 application.
The problem is that before every request made by the client a preflight request is sent to the server.
This kind of request have a type OPTIONS, and it's duty of the server to send back a preflight response with status 200 and headers set for accepting requests from that client.
This is my solution (with express):
// Domain you wish to allow
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'YOUR-CUSTOM-HEADERS-HERE');
// Set to true if you need the website to include cookies in requests
res.setHeader('Access-Control-Allow-Credentials', true);
// Check if Preflight Request
if (req.method === 'OPTIONS') {
res.status(200);
res.end();
}
else {
// Pass to next layer of middleware
next();
}
As you can see, i set the headers and then fetch if the request type is OPTIONS. In that case i send back a status 200 and end the response.
In this way, the client will be authorized and you will be also able to set your custom headers in all the requests without worrying about CORS anymore.
If you really want to change withCredentials than you have to provide customized BrowserXhr, see this answer.

Connect and pass Auth token to a SignalR hub with CORS enabled

I have three ASP.NET WebAPI endpoints:
Identity server, which generates bearer tokens (serverA.com);
SignalR server with hub (serverB.com);
Some endpoint with a simple ASP.NET MVC view and SignalR JS client script (serverC.com).
All three servers use OAuth2 middleware as Auth mechanism. Microsoft.Owin.Cors is configured as well.
Servers use only HTTPS requests.
SignalR v2.2.0 is installed on serverB.com.
I can successfully make a cross-domain request from serverC.com to serverA.com to get bearer token, but, actually, I don't know how to pass auth token while connecting to serverB.com
There are two ways I found so far:
Pass auth token as a query string (not secure);
Apply this setting to the jQuery.ajax
$.ajaxSetup({
beforeSend: function (xhr) {
xhr.setRequestHeader('tokenKey', 'tokenValue');
}});
but it forces SignalR to use long polling only.
Is there any other way to send auth token (not in query string) so it can be consumed and validated by OAauth BearerAuthorizationProvider? Maybe, cookies, headers or any other way?
Update
CORS middleware set up for both environments to allow all data and accept credentials.
Here is my OWIN middleware:
var requestUri = context.Request.Uri.AbsolutePath;
if (string.Equals(requestUri, authRoute, StringComparison.OrdinalIgnoreCase))
{
if (!context.Request.Headers.ContainsKey("Authorization") || string.IsNullOrEmpty(context.Request.Headers["Authorization"]))
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
else
{
context.Response.Cookies.Append("BearerToken", context.Request.Headers["Authorization"]);
}
}
else
{
await Next.Invoke(context);
}
Then I do first Ajax request:
$.ajax({
url: self.communicationHubUrl + '/authenticate',
type: 'post',
cache: false,
crossDomain: true,
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', self.accessToken);
});
middleware sets auth token from header to the response cookies.
Then I call hub.Start so SignalR begins to send ajax requests.
But for some reasons I don't quite understand, cookies are present in request only if I enable xhr.withCredentials = true for ALL ajax requests via $.ajaxSetup
$.ajaxSetup({
xhrFields: {
withCredentials: true
}
});
Without this setting request doesn't include cookies. On the other hand, I guess it's not a good decision to force all ajax requests to enable such settings.
Furthermore, I've faced strange behavior in Oauth middleware: ValidateIdentity method is not invoked when request from signalR comes so instead of 401 Unauthorized, I'm getting deafult principal.
I think that putting the auth token insied a cookie will be your best bet. Unlike the ajaxSetup option, cookies are sent with EventSource and WebSocket requests.
You might need to add some middleware to the SignalR server (serverB.com) that sets the appropriate cookie when you POST the auth token before starting the SignalR connection.

Resources