Auth0 & Express-openid-connect /callback not responding - node.js

I have a simple node express application running on port 3002 (because 3000 is already used). For logging users in I use Auth0 and the Express-openid-connect package.
Every time I try to log in I get stuck at a blanc page called: Submit This Form, but it doesn't stop loading. The logs in Auth0 always show: successfull login. I can however access non login-protected routes without a problem.
The /callback route throws the following error:
Error: Request aborted
at IncomingMessage.<anonymous> (/workspace/node_modules/formidable/lib/incoming_form.js:122:19)
at IncomingMessage.emit (events.js:400:28)
at abortIncoming (_http_server.js:569:9)
at socketOnEnd (_http_server.js:585:5)
at Socket.emit (events.js:412:35)
at endReadableNT (internal/streams/readable.js:1334:12)
at processTicksAndRejections (internal/process/task_queues.js:82:21)
but I think that's what it is supposed to do when a request is aborted, right?
My Auth0 setup should be right since it doesn't result in an error.
My Express-openid-connect middleware looks linke this:
require('dotenv').config()
const { auth } = require('express-openid-connect')
module.exports = auth({
authRequired: false,
issuerBaseURL: process.env.AUTH0_ISSUER_URL,
baseURL: process.env.BASE_URL,
clientID: process.env.AUTH0_CLIENT_ID,
secret: process.env.AUTH0_SECRET
})
and the env variables:
BASE_URL=http://localhost:3002
AUTH0_ISSUER_URL=...
AUTH0_CLIENT_ID=...
AUTH0_SECRET=H+F/0yW/i4X11EDzAFBZE2iaUTy4jBMo3gBWwXRkoY8W3DJ+E24tnt8Q5y+rF7QO
AUTH0_SECRET is just a random string but I tried it with the client secret provided by Auth0 and it changed nothing.
AUTH0_ISSUER_URL is the correct url since every request shows up in the logs.
AUTH0_CLIENT_ID is the correct client id since the logs show the correct application.
If I manually go to localhost:3002/callback I get the following expected error:
BadRequestError: state missing from the response
at /workspace/node_modules/express-openid-connect/middleware/auth.js:121:31
at processTicksAndRejections (internal/process/task_queues.js:95:5)
This means that my /callback route should be reachable and working.
However I can manually copy the parameters and then everything works however this process isn't usefull for production.
I even tried to submit the form manually over the console
document.forms[0].submit()
but it returned undefined and the page didn't stop loading once again.
So for me there is something wrong with the submit() function of that form.

My understanding of this is that Express OpenID Connect wraps and handles most of the things so developers would not have trouble to setup the protocol flow. Meaning, you would not need to access /callback by yourself. auth from express OpenID Connect, once it is added in express app, provides our express app /login, /callback and /logout routes. More you can find here https://auth0.com/docs/quickstart/webapp/express.
There are 2 points, in your question that i see, which shall be discussed.
The logs in Auth0 always show: successfull login. I can however access non login-protected routes without a problem.
You can access your routes because you are logged in. How you can log out? You need auth0Logout: true, in configuration object for auth function. Logout route is provided under the hood by Express openID Connect auth method https://auth0.com/docs/quickstart/webapp/express/01-login#logout
You should not call /callback by yourself, instead it is part of the flow which is provided by the library (express openID Connect). You can download their sample project and try it yourself: https://github.com/auth0-samples/auth0-express-webapp-sample/tree/master/01-Login. You can see there that /callback route is not added by sample project code. Same goes for /login and /logout routes.
Screenshots after sample project from the link is run:
when login is clicked sequence is like this: /login, /callback, and then /
in network tab you can see more

Related

Confidential Rest-Api w/ Permissions - Always 403s - What Am I Doing Wrong?

I've tried for many hours now and seem to have hit a wall. Any advice/help would be appreciated.
Goal: I want to authorize the express rest-api (ex client-id: "my-rest-api") routes (example resource: "WeatherForecast") across various HTTP methods mapped to client scopes (examples: "create"/"read"/"update"/"delete"). I want to control those permissions through policies (For example - "Read - WeatherForecast - Permission" will be granted if policy "Admin Group Only" (user belongs to admin group) is satisfied.
Rest-api will not log users in (will be done from front end talking directly to keycloak and then they will use that token to talk with rest-api).
Environment:
Keycloak 15.1.1 running in its own container, port 8080, on docker locally (w/ shared network with rest-api)
"my-rest-api": Nodejs 16.14.x w/ express 4.17.x server running on its own container on docker locally. Using keycloak-connect 15.1.1 and express-session 1.17.2.
Currently hitting "my-rest-api" through postman following this guide: https://keepgrowing.in/tools/kecloak-in-docker-7-how-to-authorize-requests-via-postman/
What Happens: I can login from keycloak login page through postman and get an access token. However when I hit any endpoint that uses keycloak.protect() or keycloak.enforce() (with or without specifying resource permissions) I can't get through. In the following code the delete endpoint returns back 200 + the HTML of the keycloak login page in postman and the Get returns back 403 + "Access Denied".
Current State of Realm
Test User (who I login with in Postman) has group "Admin".
Client "my-rest-api" with access-type: Confidential with Authorization enabled.
Authorization set up:
Policy Enforcement Mode: Enforcing, Decision Strategy: Unanimous
"WeatherForecast" resource with uri "/api/WeatherForecast" and create/read/update/delete client scopes applied.
"Only Admins Policy" for anyone in group admin. Logic positive.
Permission for each of the client scopes for "WeatherForecast" resource with "Only Admins Policy" selected, Decision Strategy: "Affirmative".
Current State of Nodejs Code:
import express from 'express';
import bodyParser from 'body-parser';
import session from "express-session";
import KeycloakConnect from 'keycloak-connect';
const app = express();
app.use(bodyParser.json());
const memoryStore = new session.MemoryStore();
app.use(session({
secret: 'some secret',
resave: false,
saveUninitialized: true,
store: memoryStore
}));
const kcConfig: any = {
clientId: 'my-rest-api',
bearerOnly: true,
serverUrl: 'http://localhost:8080/auth',
realm: 'my-realm',
};
const keycloak = new KeycloakConnect({ store: memoryStore }, kcConfig);
app.use(keycloak.middleware({
logout: '/logout',
admin: '/',
}));
app.get('/api/WeatherForecast', keycloak.enforcer(['WeatherForecast:read'],{ resource_server_id: "my-rest-api"}), function (req, res) {
res.json("GET worked")
});
app.delete('/api/WeatherForecast', keycloak.protect(), function (req, res) {
res.json("DELETE worked")
});
app.listen(8081, () => {
console.log(`server running on port 8081`);
});
A Few Other Things Tried:
I tried calling RPT endpoint with curl using token gotten from postman and got the RPT token perfectly fine, saw permissions as expected.
I tried calling keycloak.checkPermissions({permissions: [{id: "WeatherForecast", scopes: ["read"]}]}, req).then(grant => res.json(grant.access_token)); from inside an unsecured endpoint and got "Connection refused 127.0.0.1:8080".
I tried just disabling Policy Enforcement Mode just to see, still got Access Denied/403.
I tried using keycloak.json config instead of object method above - same exact results either way.
I tried openid-client (from another tutorial) and also got connected refused issues.
I've tried using docker host ip, host.docker.internal, the container name, etc. to no avail (even though I don't think it is an issue as I obviously can hit the auth service and get the first access token).
I really want to use Keycloak and I feel like my team is so close to being able to do so but need some assistance getting past this part. Thank you!
------------------- END ORIGINAL QUESTION ------------------------
EDIT/UPDATE #1:
Alright so a couple more hours sank into this. Decided to read through every line of keycloak-connect library that it hits and debug as it goes. Found it fails inside keycloak-connect/middleware/auth-utils/grant-manager.js on the last line of checkPermissions. No error is displayed or catch block to debug on - chasing the rabbit hole down further I was able to find it occurs in the fetch method that uses http with options:
'{"protocol":"http:","slashes":true,"auth":null,"host":"localhost:8080","port":"8080","hostname":"localhost","hash":null,"search":null,"query":null,"pathname":"/auth/realms/my-realm/protocol/openid-connect/token","path":"/auth/realms/my-realm/protocol/openid-connect/token","href":"http://localhost:8080/auth/realms/my-realm/protocol/openid-connect/token","headers":{"Content-Type":"application/x-www-form-urlencoded","X-Client":"keycloak-nodejs-connect","Authorization":"Basic YW(etc...)Z2dP","Content-Length":1498},"method":"POST"}'
It does not appear to get into the callback of that fetch/http wrapper. I added NODE_DEBUG=http to my start up command and was able to find that swallowed error, which appears I am back to the starting line:
HTTP 31: SOCKET ERROR: connect ECONNREFUSED 127.0.0.1:8080 Error: connect ECONNREFUSED 127.0.0.1:8080
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1157:16)
at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17)
I then saw something that I thought may be related due to my docker network set up (Keycloak and Spring Boot web app in dockerized environment) and tried change host name dns so I could use something other then local host but it didn't work either (even added to redirect uri, etc.).
UPDATE #2:
Alright so I got the keycloak.protect() (pure authentication) endpoint working now. I found through reading through the keycloak-connect lib code more options and it seems that adding "realmPublicKey" to the keycloak config object when instantiating keycloak-connect fixed that one. Still no luck yet on the authorization keycloak.enforce side.
const kcConfig: any = {
clientId: 'my-rest-api',
bearerOnly: true,
serverUrl: 'http://localhost:8080/auth',
realm: 'my-realm',
realmPublicKey : "MIIBIjANBgk (...etc) uQIDAQAB",
};
So my team finally figured it out - the resolution was a two part process:
Followed the instructions on similar issue stackoverflow question answers such as : https://stackoverflow.com/a/51878212/5117487
Rough steps incase that link is ever broken somehow:
Add hosts entry for 127.0.0.1 keycloak (if 'keycloak' is the name of your docker container for keycloak, I changed my docker-compose to specify container name to make it a little more fool-proof)
Change keycloak-connect config authServerUrl setting to be: 'http://keycloak:8080/auth/' instead of 'http://localhost:8080/auth/'
Postman OAuth 2.0 token request Auth URL and Access Token URL changed to use the now updated hosts entry:
"http://localhost:8080/auth/realms/abra/protocol/openid-connect/auth" -> "http://keycloak:8080/auth/realms/abra/protocol/openid-connect/auth"
"http://localhost:8080/auth/realms/abra/protocol/openid-connect/token" ->
"http://keycloak:8080/auth/realms/abra/protocol/openid-connect/token"

React + Express With Passport.js

I am setting up a full-stack application using React and Express JS.
I'm using Passport.js for authentication and have come across a slight problem...
So my front-end and back-end are two separate packages running on two different ports. On my express app, I have created a route like the following.
app.post('/api/account/login', (req, res, next) => {
passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/users/login',
}) (req, res, next);
});
This is pretty standard as far as Passport.js goes. Basically, if it authenticates the credentials I have provided, then it should redirect me to /dashboard. If not, then to the other mentioned route.
I am able to call reach this endpoint from my react application and get the correct response like the following in the network tab of chrome.
Request URL: http://localhost:3000/dashboard
Request Method: GET
Status Code: 304 Not Modified
Remote Address: 127.0.0.1:3000
Referrer Policy: no-referrer-when-downgrade
However, it doesn't actually redirect me to /dashboard. Is it not possible to do it this way?
Thanks.
It sounds like your React app is calling the route via ajax using something like fetch.
The way you're using Passport assumes that a browser is issuing the requests directly. On a successful login, Passport returns a Redirect response (HTTP 302 or similar), which the browser honors and redirects the user to.
Ajax requests don't work this way, since there isn't any navigation happening.
You'll need to handle this yourself on the React side of things. Your Express app will need to handle the session authentication by (for instance) returning a JSON message with a token or storing a session cookie. You'll need to update your React app to recognize this and then navigate to the correct route via client-side Javascript.
If you're using react-router, they have some sample code that might be helpful.

how to use passport SAML with both node (express) as backend and vue as client

I have an app with node (express) backend, and vue client.
I'm trying to add SAML SSO using passport. (makes sense to do it on the server node app).
it works perfect when used in express app. but when I applied it to a structure of express backend and vue client - it fails to make the redirection to the Idp.
when user enters my login page, vue client (Login.vue) calls node backend for verifying the user. (api verifyuser)
node call passport.authenticate('saml', ...) and I expected a response I can send back to the vue function that called me, and there, in Login.vue - to make the redirection.
but here comes the problem:
in the backend node app, the redirect response is sent after my code is executed, inside passport strategy. So it is sent automatically to the browser, not returning to the vue script that called this node api.
So the redirection is done in the background, the user don't see any redirect. the original login page is still shown.
And my vue function gets the response back from the API - only after the browser sends the redirect (in the background) to the IDP, and gets the login html page response from the IDP.
So the data I get back - is an html of the IDP login page, instead of a redirection data.
How can I solve it?
I'm new to client technologies and js and node including, so I really don't know how such a flow should be handled. searching 3 days for solution.
Thanks a lot for you assistance!
here is my snippets of code:
Login.vue:
<input class="button wide cropBottom io-mango ae-5 margin-top-0 toRight" v-on:click="userLogin" type="button" value="Log In"/>
...
userLogin: function() {
...
...
$(".overlay").show();
this.$http.post(process.env.BASE_URL + "verifyuser", oUser) //call backend node express app
.then(function(data) {
...
here I gets only an html login page which the IDP sent as a response to the redirect with the SAML Request.
}
Backend node express app:
verifyuser.js:
module.exports = function (app) {
app.post('/verifyuser', (req, res, next) => {
var SamlStrategy = passportSaml.Strategy;
passport.use(new SamlStrategy(
{ ...
});
passport.authenticate('saml', {session: false}, function (err, user, info) {
...
})(req,res,next);
//tried to get the redirect here, but res still don't have it. only after this function is called, the SAML request is created from the req, and the location url is made.
});
I've found a solution.
I changed the Vue client:
instead of calling the server using ajax, and expecting a data response to come back,
I called the server using post of a form.
that way, the browser redirects to the server when I call it, and when the passport library in the server returns a redirect response- it is done in the forground, and the user can see it.
In Single logout, passport have done a better job:
the passport API just returns the logout request created.
then I can decide myself if I want redirect from the server, or I want to send the redirection request to the waiting client function - and do the redirection from there.

Angular 5/Express/CORS with Facebook OAuth

So I'm having a problem getting my MEAN stack application to provide oauth with Facebook using PassportJS. Namely, I can't figure out how to get CORS to work on the Angular side of the application.
In my application the angular application sends a get request through a user facing click action:
<button (click)="onFacebookLogin()" class="loginBtn loginBtn-facebook">
<span><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/14082/icon_facebook.png"/></span>| Login with Facebook
</button>
which eventually leads to this get request in my loginService:
facebookLoginUser(): Observable <User> {
return this.http.get('users/auth/facebook')
}
This of course takes me to my route which uses passport.js:
// This part is in a 'users' module which is why you don't see 'users' prepend the route
router.get('/auth/facebook', passport.authenticate('facebook', {
scope: ['email', 'photos']
}))
Now this piece of code returns an error from Facebook saying that 'photos' is an improper scope (something that I will address later). My problem is that the request delivers no error to my server and instead delivers the error (and I would presume the eventual object) to the angular application (I see the error in the browser console). The browser, naturally, complains about this since facebook is trying to communicate with it on a request that it didn't initiate (cors). My question is, how do I fix this?
Recommendations from this SO question say that I must navigate to the suggest that I need to navigate to the page that I'm making the request from. I've tried making the button a link with an anchor element and href but that doesn't work. Also that question uses angular.js and I don't think providing a new route with my router and creating a whole new view seems very prudent. Besides that, I still feel like Facebook would be returning the user to the angular application and not my express application like I intend. Does andybody know what I should do?
Update
Error Code
Failed to load https://www.facebook.com/dialog/oauth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Ffacebook%2Fcallback&scope=email%2Cphotos&client_id=2087173708182970: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.
As CBroe commented, the method I was using to make the call was wrong as it couldn't be performed in an ahax-y way. I ended up using an anchor tag with an target=_blank attribute with an href that pointed directly at my backend passport facebook oauth route. So something like this:
<a href="http://localhost:3000/users/auth/facebook" target="_blank" class="loginBtn loginBtn-facebook">
<span><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/14082/icon_facebook.png"/></span>| Login with Facebook
</a>
Everything works now

Handling redirect code with OAuth2

I am using Instagram's API which requires OAuth2 have some questions regarding best practice for setup. For more details here: https://www.instagram.com/developer/authentication/
So a user clicks on a login button and I give a redirect href.
Log In
They then receive a popup sign in, and they have the option of signing in or not. They are then redirected and '?code=zzzzzzz'is appended to the url: "http://localhost:8080?code=zzzzzzz"
Then the instructions read:
Now you need to exchange the code you have received in the previous step for an access token. In order to make this exchange, you simply have to POST this code, along with some app identification parameters, to our access_token endpoint.
But how should I do this? I'm using Express on the backend. To serve the frontend I use this line:
var static_path = path.join(__dirname, './../build');
It isn't an API route, so I can't use the normal
app.get('/?code=zzzzzzz', function(req, res) {...}).
So how can I use the code that I received in the params?
You must change your redirect_uri on isntagram to something like:
/auth/instagram/callback
https://api.instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=http://localhost:8080/auth/instagram/callback&response_type=code
Then you can get your code with req.query.code from inside the controller.
For posting the code to isntagrams API use "request" library for nodejs

Resources