ASP.NET Core Web Api route certain requests to microservice running on other port - asp.net-core

I have created a SPA which has to communicate to various microservices in the backend.
For deployment and latency reasons, we have to make sure, that the frontend only communicates with one endpoint, which then routes to the other microservices internally.
How can i make this work with ASP.NET Core? I looked at Ocelot which provides something quite similar but as i see in the documentation i have to configure the IP adress / hostname under which the backend will be accessed from the client and i don't know this information upfront as this will be determined after deployment (and can be different for every machine, given this service will run on various Edge devices).
Can this be achieved using a simple routing middleware which looks for a certain path in the url (e.g. /api/otherservice), send a http request to the responsible microservice (e.g. http://localhost:1234/api/otherservice) and return the information to the caller?
I am using ASP.NET Core 2.1.
Update: I managed to get ocelot running to apply the desired routing to the downstream microservice. However, the service where i use Ocelot provides routes itself (serving the web app frontend and some other backend api routes).
Does anybody know how i can tell Ocelot to fall back to the routes declared in the service it is running if there is a route which is not contained in ocelot.json (or the other way round, to tell ASP.NET Core to just use ocelot for routes id can't resolve on it's own).
Update 2:
This is my ocelot config which results in an infinite loop:
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/tool/{url}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5200
}
],
"UpstreamPathTemplate": "/api/tool/{url}",
"UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ]
},
{
"DownstreamPathTemplate": "/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5100
}
],
"UpstreamPathTemplate": "/{everything}",
"Priority": 0
}
]
}
Omitting the second (catchAll) - route successfully routes to the tool-route, but can't serve routes provided by the ASP.NET Core Service which contains the Ocelot Middleware.

Maybe you want a catch all reroute? Since this type of reroute have the least priority it will only do it if all previous reroutes with higher priority fail:
https://ocelot.readthedocs.io/en/latest/features/routing.html#catch-all
{
"DownstreamPathTemplate": "/{url}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 80,
}
],
"UpstreamPathTemplate": "/{url}",
"UpstreamHttpMethod": [ "Get" ]
}
If you don't want this default behavior you can take a look at the priority attribute:
https://ocelot.readthedocs.io/en/latest/features/routing.html#priority
{
"UpstreamPathTemplate": "/goods/{catchAll}"
"Priority": 0
}

Related

Rest API for Authentication with nHost

So I know there's several SDK packages for many languages available for nHost, however I need to create my own interface to the system since the language I'll be using isn't typical.
I basically just need to know how to interact with authentication endpoints, send a users un/pw and recieve a JWT token. I've been successfully able to do this with aws Cognito, but I'd like to explore this instead.
I'm also not sure if I'm using the right base url, here's my thought so far:
https://kbvlufgpikkxbfkzkbeg.nhost.run/auth/login
So I would POST to there with some json in the body with the un/pw stuff, and the response should be the jwt token right?
I get a "resource does not exist" response from the above, however, so obviously I'm not forming the url correctly in the first place.
Thanks for the help!
Nhost supports multiple sign-on methods.
For example, using the email+password method, you would send:
POST https://xxxxxxxxxxxxx.nhost.run/v1/auth/signin/email-password
{"email":"foo#example.com","password":"bar"}
and the response:
{
"session": {
"accessToken": "somejwt....",
"accessTokenExpiresIn": 900,
"refreshToken": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"user": {
"id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"createdAt": "2022-09-17T19:13:15.440221+00:00",
"displayName": "foo#example.com",
"avatarUrl": "",
"locale": "en",
"email": "foo#example.com",
"isAnonymous": false,
"defaultRole": "user",
"metadata": {},
"emailVerified": true,
"phoneNumber": null,
"phoneNumberVerified": false,
"activeMfaType": null,
"roles": [
"user",
"me"
]
}
},
"mfa": null
}
The JWT is short-term, when it expires, the refresh token is used to get a new one.
The Nhost JavaScript SDK handles it automatically for you, that's a big benefit to the platform (in addition to being integrated with Hasura). If you are trying to port it to another unsupported language, you'd have to reimplement it. Probably by reading the library and/or running one of their sample client application and reverse-engineering the HTTP over the wire.

Create Controller Service using Nifi API REST

I am trying to create a controller service using nifi api rest but I am blocked because when I try:
InvokeHTTP
POST
https://hostname/nifi-api/controller/controller-services
using this json
{
"revision": {
"version": 0
},
"disconnectedNodeAcknowledged": false,
"component": {
"name": "DMCS_try",
"type": "org.apache.nifi.distributed.cache.server.map.DistributedMapCacheServer",
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-distributed-cache-services-nar",
"version": "1.9.0.3.4.1.1-4"
},
"state": "ENABLED",
"properties": {
"Port": "4555",
"Maximum Cache Entries": "10000",
"Eviction Strategy": null,
"Persistence Directory": null,
"SSL Context Service": null
}
}
}
I got this "error"
Node XXXXXXXXX is unable to fulfill this request due to: Unable to modify the controller. Contact the system administrator. Contact the system administrator.
Controller services can be created in two different places. One place is in the flow as part of a process group so they can be used by processors, the other place is at the controller level for use by reporting tasks.
The URL you specified is for creating a service at the controller level and therefore whatever identity you are using to authenticate as need permissions to modify the controller (WRITE on /controller). The error message is saying you don't have that permission.

Extracting custom objects from HttpContext

Context
I am rewriting an ASP.NET Core application from being ran on lambda to run on an ECS Container. Lambda supports the claims injected from Cognito Authorizer out of the box, but Kestrel doesn't.
API requests are coming in through API Gateway, where a Cognito User Pool authorizer is validating the OAuth2 tokens and enriching the claims from the token to the httpContext.
Originally the app was running on lambda where the entry point was inheriting Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction, which extracts those claims and adds them to Request.HttpContext.User.Claims.
Kestrel of course doesn't support that and AWS ASPNET Cognito Identity Provider seems to be meant for performing the same things that the authorizer is doing.
Solution?
So I got the idea that maybe I can add some custom code to extract it. The HTTP request injected into lambda looks like this, so I expect it should be the same when it's proxied into ECS
{
"resource": "/{proxy+}",
"path": "/api/authtest",
"httpMethod": "GET",
"headers": {
<...>
},
"queryStringParameters": null,
"pathParameters": {
"proxy": "api/authtest"
},
"requestContext": {
"resourceId": "8gffya",
"authorizer": {
"cognito:groups": "Admin",
"phone_number_verified": "true",
"cognito:username": "normj",
"aud": "3mushfc8sgm8uoacvif5vhkt49",
"event_id": "75760f58-f984-11e7-8d4a-2389efc50d68",
"token_use": "id",
"auth_time": "1515973296",
"you_are_special": "true"
}
<...>
}
Is it possible, and how could I go about it to add all the key / value pairs from requestContext.authorizer to Request.HttpContext.User.Claims?
I found a different solution for this.
Instead of trying to modify the HttpContext I map the authorizer output to request headers in the API Gateway integration. Downside of this is that each claim needs to be hardcoded as it doesn't seem to be possible to iterate over them.
Example terraform
resource "aws_api_gateway_integration" "integration" {
rest_api_id = "${var.aws_apigateway-id}"
resource_id = "${aws_api_gateway_resource.proxyresource.id}"
http_method = "${aws_api_gateway_method.method.http_method}"
integration_http_method = "ANY"
type = "HTTP_PROXY"
uri = "http://${aws_lb.nlb.dns_name}/{proxy}"
connection_type = "VPC_LINK"
connection_id = "${aws_api_gateway_vpc_link.is_vpc_link.id}"
request_parameters = {
"integration.request.path.proxy" = "method.request.path.proxy"
"integration.request.header.Authorizer-ResourceId" = "context.authorizer.resourceId"
"integration.request.header.Authorizer-ResourceName" = "context.authorizer.resourceName"
"integration.request.header.Authorizer-Scopes" = "context.authorizer.scopes"
"integration.request.header.Authorizer-TokenType" = "context.authorizer.tokenType"
}
}

How to ask to the service worker to ignore requests matching a specific URL pattern in Polymer?

My application is built on Polymer v2 and uses the Firebase Auth service for authentication. Actually, I use the login-fire element. For a better experience on mobile devices, I choose to sign-in with redirect.
In the "network" tab of the DevTool (in Chrome) I see that a request containing the /__/auth/handler? pattern is sent for requesting Google authentication (for example, if the provider used is Google).
With the service workers enabled, this request is caught and the response is the login page of my application. No login attempted, the response comes from the service worker and I get a Network Error from Firebase API because of a timeout.
When I deploy the app without service workers the authentication process is working and I can reach the app.
I tried many ways to config the service workers to ignore all requests to a URL with the /auth/ pattern but I failed.
See the last version of my config file bellow.
sw-precache-config.js
module.exports = {
globPatterns: ['**\/*.{html,js,css,ico}'],
staticFileGlobs: [
'bower_components/webcomponentsjs/webcomponents-loader.js',
'images/*',
'manifest.json',
],
clientsClaim: true,
skipWaiting: true,
navigateFallback: 'index.html',
runtimeCaching: [
{
urlPattern: /\/auth\//,
handler: 'networkOnly',
},
{
urlPattern: /\/bower_components\/webcomponentsjs\/.*.js/,
handler: 'fastest',
options: {
cache: {
name: 'webcomponentsjs-polyfills-cache',
},
},
},
{
urlPattern: /\/images\//,
handler: 'cacheFirst',
options: {
cacheableResponse: {
statuses: [0, 200],
},
},
},
],
};
Do you have a better solution? Do you notice what I missed?
Thank you for your help.
You can add this to your sw-precache-config.js file
navigateFallbackWhitelist: [/^(?!\/auth\/)/],
You should only whitelist the paths of your application. This should be known to you.
So everything you do not whitelist, will not be served from the serviceworker.
navigateFallbackWhitelist: [/^\/news\//,/^\/msg\//, /^\/settings\//],
With this example, only news/*, msg/*,settings/* will be delivered.
/auth/*,/api/*,... will not be caught.

Calls to CouchDB's _session always return 200

Is it right that calls to the CouchDB _session endpoint always seem to return a 200 HTTP status code, even if the cookie I'm passing is absent or wrong?
Passing a wrong cookie or no cookie in my GET request headers always seems to return:
{
"ok": true,
"userCtx": {
"name": null,
"roles": []
},
"info": {
"authentication_db": "_users",
"authentication_handlers": [
"cookie",
"default"
]
}
}
When passing the correct cookie, I receive a slightly different response:
{
"ok": true,
"userCtx": {
"name": "jack",
"roles": []
},
"info": {
"authentication_db": "_users",
"authentication_handlers": [
"cookie",
"default"
],
"authenticated": "cookie"
}
}
Is this standard behaviour? If it is, which key should I rely on to assume a successful authentication? Should it be res.userCtx.name or res.info.authenticated?
This appears to be standard behavior, although it's not explicitly stated in the documentation.
However, you can tell CouchDB to return a 401 response by setting basic=true in the query like this: /_session?basic=true.
It seems to suggest that it's useful for basic authentication, but it also works with a cookie authenticated user. (tested with CouchDB 2.0.0)