Express application for Authentication as seperate microservice - node.js

Current Situation:
I am currently working with a specific oauth provider and i am hosting my applications as microservices in a kubernetes cluster.
My end user is actively working with an angular application hosted as a docker container using nginx as webserver.
Now my idea was to integrate the authentication as a seperate microservice using node.js express and passport. so The workflow would be
User hits login in angular and gets redirected to the express application (same host address just a different endpoint /auth/someProvider)
The express application has no user interface it just handles all the oauth redirecting and communication with the provider, after the user information has been collected it redirects back to the angular application.
Now this works pretty for one last part. When my /auth/provider/callback would redirect inside of the express application it is very easy to access the request object that has been extended with the user object. when I redirect to an external website I get the cookie and everything but not an easy way to access the user object.
My acutal question(s):
Is there a safe way to pass that user information from the Request object directly to be used by the angular application (Best way i could think of is to use the headers as they are encrypted as well in https but still seems kind of hacky).
Is ita good idea in general to use OAuth that way.
The big advantage to this solution would be that I could use the same Docker Container with many web projects not having to implement Authentication one by one by just changing ClientId and Secret Env Vars in that Docker Container.

OK this is how I made it work.
basically you implement the basic concept described in the passport.js Documentation and add a seperate endpoint to access the userinfo. This is why I will describe where it starts to differ a bit
Step 1: Authenticate user at the Gateway
As described in the passport.js documentation. The authentication microservice needs the following callback (The router here is already served under /auth)
router.get("/provider/callback", passport.authenticate("provider", {
failureRedirect: "https://your-frontent-app.com/login",
}), (req, res) => {
res.redirect("https://your-frontend-app.com");
});
When you get back to your Web-Application you will see the Session-Cookie has successfully been stored.
Step2: Get User Info from Endpoint
Now we need the second endpoint /auth/userinfo in our routes.
router.get('/userinfo', (req, res) => {
if(!req.session) return res.status(401).send();
if(!req.session.passport) return res.status(401).send();
if(!req.session.passport.user) return res.status(401).send();
return res.json(req.session.passport.user).status(200).send();
});
Not very pretty this block with the 3 if's but it happened to me that all of those combinations could be undefined
Now, with the session cookie stored in our browser we can call that endpoint from our front-end with the credentials (I'll use axios for that)
axios.get('https://your-authenticator.com/auth/userinfo', {withCredentials: true})
.then(res => {
//do stuff with res.data
});
Now theres one more thing to note. If you want to use credentials to call that API setting the Access-Control-Allow-Origin Header to * will not work. You will have to use the specific host you'll be calling from. Also you will need to allow the Credentials in the Header. So back in your main Express app make sure you use the headers like
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "https://your-frontend-app.com");
res.header("Access-Control-Allow-Credentials", "true");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Auth, Authentication, Authorization, Credentials");
next();
});

Related

How to include acr_values in Passport authenticate

I have Passport set up and working using an oauth2 strategy (code flow) against Identity Server 4. I need to include acr_values in the call to the Identity Server (this is working from another client correctly).
My understanding (as shown here for example) is that the following code should add acr_values to the auth call:
app.get('/auth', (req, res, next) => {
passport.authenticate('oidc', { acr_values: 'urn:grn:authn:fi:all' })(req, res, next);
});
But this doesn't work. Running the code from that sample also doesn't work.
Is this something I'm doing wrong, or has it changed?
How can I have passport include acr_values in the call to the Identity Server?
The acr_values format you use is not valid, it must be a single string which is space separated like: acr_1 acr_2 tenant:tenant_value idp:idp_value
Per Docs:
acr_values
allows passing in additional authentication related information - identityserver special cases the following proprietary acr_values:
idp:name_of_idp bypasses the login/home realm screen and forwards the user directly to the selected identity provider (if allowed per client configuration)
tenant:name_of_tenant can be used to pass a tenant name to the login UI
Here is where in code it gets validated: https://github.com/IdentityServer/IdentityServer4/blob/main/src/IdentityServer4/src/Validation/Default/AuthorizeRequestValidator.cs#L771
Example in tests: https://github.com/IdentityServer/IdentityServer4/blob/main/src/IdentityServer4/test/IdentityServer.IntegrationTests/Endpoints/Authorize/JwtRequestAuthorizeTests.cs#L667

How to restrict access by IP in a gRPC server?

I'm a beginner in gRPC and as my first challenge, I'm building a Node JS platform composed of some gRPC microservices (according to API-Gateway pattern). I would like to restrict all access from external sources - only the gateway itself will be able to reach my internal struct.
After some time searching, I found out 3 ways to limit the access:
1 - HTTP authentication;
2 - Token authentication;
3 - TSL/SSL authentication;
My Gateway already has an auth mechanism - JWT middleware. I don't want to copy it for each microservice and generate a lot of code redundancy.
I would like to get some way to filter my requests by IP in each internal microservice and allow or disallow its access. In a nutshell, I want to ensure that only the Gateway IP can access all internal microservices.
Here, the component diagram showing my initial architecture:
We can do it easily in an Express API:
// Express example
app.use(function (req, res, next) {
if (req.ip !== '1.2.3.4') { // Wrong IP address
res.status(401);
return res.send('Permission denied');
}
next(); // correct IP address, continue middleware chain
});
Is there some way to build something like that using gRPC?
Thank you very much.

how to verify a firebase admin on backend

I'm trying to implement middleware in an express server that sets custom uid/admin headers on the incoming request. This modified request will then be used after the middleware to see if an authenticated user/admin is accessing that particular resource.
To do this for a client, I just grab the token on the Authorization header and feed it into the firebase admin api's verifyIdToken method. If a uid exists, I set the header. For example:
app.use((req, res, next) => {
/* get rid of headers sent in by malicious users. */
delete req.headers._uid;
try {
const token = req.headers.authorization.split(' ')[1];
_dps.fb_admin.auth().verifyIdToken(token).then(claims => {
if (claims.uid) { req.headers._uid = claims.uid; }
next();
}).catch(err => next());
} catch(err) { next(); }
});
Two questions:
1) As an admin with a service account on another server, how would I send a request to this server such that this server can determine an admin sent the request?
2) How would I identify the admin on this server?
You will need to create your own custom Firebase token to include custom fields such as isAdmin: true in the JWT. See https://firebase.google.com/docs/auth/admin/create-custom-tokens
See (1)
Use the setCustomUserClaims() API to add a special "admin" claim to all admin user accounts, and check for it when verifying ID tokens. You can find a discussion and a demo of this use case here (jump ahead to the 6:45 mark of the recording).
Perhaps a solution would be to simply generate an API key of decent length and set it as an environment variable on each of my servers. I could then send this in the Authorization header whenever i want to make an admin https request and verify it in the middleware of the receiver by doing a simple string compare. The only people that could see this API key are those that have access to my servers (AKA admins). Let me know if something is wrong with this approach. It sure seems simple.

Node.js Express - sharing Passport sessions on different servers

So we have a "web" server and an API server - the web server serves up HTML and the API server serves up JSON.
We use Passport's Twitter strategy to authenticate on the web server.
My question is - what is the best way to check on the API server that the user who has authenticated with the web server is also authenticated with the API?
My assumption is that we should put most of the Passport code into the web server, have the user authenticate with it as usual with Passport, and use some middleware in the API server like so to check if the user is logged in (has a session):
app.use(passport.initialize());
app.use(passport.session());
app.use(expressSession({
secret: 'keyboard cat',
store: new MongoStore({mongooseConnection: mongoose.connection})
}));
app.use(function(req,res,next){
if(req.isAuthenticated()){
next();
}
else{
console.log('unauthenticated request');
res.status(403).json({error:'unauthorized request'});
}
});
however, the above doesn't seem to be enough. I am utterly confused about exactly what code I need on the API server - I believe I need to read from the same session store that the web server writes to and to look at a token in the request to the API server and compare it with a token in the session store?
I am not sure I understand what the req.isAuthenticated() function does? It seems like I now need to write my own req.isAuthenaticated() function which reads from the session store asynchronously...
Does anyone have an example of how to do this right?
You might be able to do as you said - authenticate using the web server and just verify that they are authenticated using the API.
Providing that both servers share the same remote session store, and both endpoints have access to the express session cookie, you should be able to implement something like this for the API:
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated())
return next();
else
// Not authenticated
}
Or
if (req.user) {
// logged in
} else {
// not logged in
}
As middleware (as you showed in your example), or on a per-route basis.
This may not be viable if you can't access the same, shared, session cookie though. Other options may be available depending on what structure your app is built in ie. - if the web server is speaking directly to the API.
You can read the raw req.isAuthenticated function from the passport github.

Web application authentication strategies

I'm looking for some advice with authentication for my web app. I'm using Node, Express and Passport to build out this app
The app has a REST API using Basic Auth (no session creation), and hosts several Angular.js web pages using form Auth (with session creation).
I would like the Angular pages to connect to the REST API, which is using a different Auth strategy. It seems I have two options:
Create a custom Basic Auth middleware, (because Passport doesn't do this out of the box). This will do session Auth if request has one, otherwise standard Basic Auth
Expose two API's one with Basic Auth (for external use) and one with form Auth (for the app pages)
If also heard that using OAuth2 might be an option, but surely that only makes sense for authenticating with a third party?
My current solution has been to perform mixed auth (session and basic) on the rest api. If a session exist continue, otherwise perform basic auth. As follows:
api.coffee:
app.api.external.get("/agents", [auth.basic], (req, res) ->
res.json myListOfAgents
auth_middleware.coffee
basic: (req, res, next) ->
if req.isAuthenticated()
return next()
else
return passport.authenticate('basic', { session: false })(req, res, next)

Resources