Keycloak Bearer only nodejs adapter returns 403 - keycloak

i'm running keycloak 15.0.2 together with a react application and a node-js backend service in kubernetes.
I have 2 clients, one is public (react-frontend) and the other one is bearer-only (service-backend), nothing fancy.
Now i'm logging in and getting the token:
Then I'm Requesting a resource from the backend via GET Method:
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: "https://example.com/api",
prepareHeaders(headers){
headers.set('Accept', 'application/x-www-form-urlencoded');
headers.set('Authorization', 'bearer ' + keycloak.token)
return headers;
}
}),
endpoints(builder){
return{
fetchListings: builder.query({
query: () => '/listings/'
}),
...
Backend-Client-Install json
{
"realm": "MyRealm",
"bearer-only": true,
"auth-server-url": "https://auth.example.com/auth/",
"ssl-required": "external",
"resource": "listings-backend",
"verify-token-audience": true,
"use-resource-role-mappings": true,
"confidential-port": 0
}
I always get a 403 access denied answer. Same with Postman.
If i remove keycloak.protect() everything works fine.

Ok guys, now after struggling some days with this problem, the whole time the roles in the front-end client resulted in the 403 access denied error.
After deleting all roles, it worked.

Related

axios not sending cookie to nestjs from NextJs

Have a front end nextJs running on a different port to the backend nestjs.
Within the nextJs session-cookie I have 2 JWT tokens access and refresh.
I can extract the access token from the next-auth session-token but axios will not send to nestjs.
If I use the { withCredentials: true } the whole next-auth token is sent but if I use the headers object nothing is received
const data = await axios.post(
process.env.NEXT_PUBLIC_SH_API_BASEURL + '/blog',
{ formData },
//{ withCredentials: true }
{
headers: {
Cookie:
'Authentication=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEzNTQ1N2FiLWI4MGUtNDU2OC1hY2RiLWNiODZmYTJlNGQxMyIsImlhdCI6MTY1MzU0NjM0NiwiZXhwIjoxNjU0NDQ2MzQ2fQ.fvnRXYwheuIOHvlTZRGqBiVR98JxdT7UqZbc6SAvcAk; Path=/; HttpOnly;'
},
}
);
nestJS - log when using with credentials
{
'next-auth.csrf-token': '493deccd24c0165f8afe08db04352415c46c7a6f150f9c51320aea0d5444589d|51953c1c056c986b799afb9dcea8c94469d35b4aab291b02ee343057fa80a70e',
'next-auth.callback-url': 'http://localhost:3000/auth/signin?callbackUrl=http://localhost:3000/',
'next-auth.session-token': 'eyJhbGc
}
But if use the headers I get
[Object: null prototype] {}
The nestJs logging is done using:
jwtFromRequest: ExtractJwt.fromExtractors([
(request: Request) => {
console.log('JWT strategy auth cookie');
console.log(request?.cookies);
return request?.cookies?.Authentication;
},
]),
If I make a call from postman there is no issue with processing the header cookie.
Could you let me know how to get axios to send the header cookie only?
Thanks

Cookies not sent with cross-origin-request

After days of searching and trying, I'm here now.
I deployed the React Application in Netlify and the Node.js backend in Heroku. Everything works fine in localhost environment. But after deploying, cookies are not sent in request header.
CORS:(Backend Node.js)
app.use(cors({
origin: CORS_ORIGIN,
credentials: true,
allowedHeaders: "Content-Type,Accept,User-Agent,Accept-Language,Access-Control-Allow-Origin,Access-Control-Allow-Credentials,cache-control"
}));
Axios:
import axios from "axios";
axios.defaults.withCredentials = true;
export default axios.create({
baseURL: process.env.REACT_APP_BACKEND + "/api",
headers: {
"Content-type": "application/json",
"Access-Control-Allow-Origin": process.env.REACT_APP_BACKEND,
},
});
Fetching Data(Mutation): apiClient is imported from above Axios.js and cookies is handled by react-cookies
apiClient.post("/auth/login",{ email: "name#mail.com", password: "pawspaws" })
.then((res) => {
setCookie("jwt", res.data.accessToken, { path: process.env.REACT_APP_PATH || "/", domain: process.env.REACT_APP_DOMAIN, maxAge: 26000, secure: true, sameSite: 'None' });
setCookie("refresh", res.data.refreshToken, { path: process.env.REACT_APP_PATH || "/", domain: process.env.REACT_APP_DOMAIN, maxAge: 2600000, secure: true, sameSite: 'None' });
setCookie("user", res.data.user, { path: process.env.REACT_APP_PATH || "/", domain: process.env.REACT_APP_DOMAIN, maxAge: 26000, secure: true, sameSite: 'None' });
}).catch((err) => console.log(err.response))
Above code sets the cookies.. and it's working.
Now, below is the request I'm sending which doesn't send the Cookies along with the request:
apiClient.get("/posts/timeline", { params: { email: "name#mail.com" } })
.then((res) => { console.log(res.data); })
.catch((err) => { console.log(err.response) });
Well, it returns unauthorized since the Cookie is not sent at all..
Ok, i found my own solution.. It was pretty easy.. It was setting up proxy
Just added line below to package.json in frontend:
"proxy":"https://random.name.herokuapp.com",
FYI, for Netlify, we need additional _redirects file in public folder.
_redirects
/api/* https://random.name.herokuapp.com/api/:splat 200
/* /index.html 200
In my case I had hardcoded my front end api calls to the herokubackend
axios.get("https://infinite-ocean-8435679.herokuapp.com/api/user")
First removed the hrokupart from the request like this
axios.get("/api/loguserin")
Then added the proxy value to the bottom of the package.json
"proxy":"https://infinite-ocean-8435679.herokuapp.com",
Next added a file called _redirects inside the react public folder.(_redirects is the file name and it has no extension such as .txt so don't add _redirects.txt )
Add the following inside _redirects file
/api/* https://infinite-ocean-8435679.herokuapp.com/api/:splat 200
/* /index.html 200
inside _redirects file formatting will be weird just add above code replacing heroku part with your backend url.
React automatically add all the files inside the public folder to its build folder once created.
deploy the site to Netlify

Cloud Run Service requests always fail with 401 when using JWT returned for another service. The same request works with JWT returned for end-user

I have an app consisting of multiple Cloud Run Services. Each service is open only to a specific set of other services.
Let's say I have 2 services with URLs https://api-service-123.run.app and https://data-provider-service-123.run.app. The first service also has a custom URL https://api.my.domain.
api-service needs access to data-provider-service. So, following the documentation, I created two per-service user-managed service-accounts api-service-account#domain and data-provider-service-account#domain. I added api-service-account#domain into data-provider-service with Cloud Run Invoker role.
Now, I have trouble accessing the account with ID Token returned from Google. I tried several ways. Firstly, I utilized slightly adjusted code from the docks
export const getToken = async (url: string, targetAUD?: string) => {
const auth = new GoogleAuth();
const request = async () => {
if (!targetAUD) {
targetAUD = new URL(url).origin;
}
console.info(`request ${url} with target audience ${targetAUD}`);
const client = await auth.getIdTokenClient(targetAUD);
const res = await client.request({url});
console.info(res.data);
return res.data;
}
try {
return await request();
} catch (err) {
console.error(err);
}
}
When the function above is called as getToken('http://data-provider-service-123.run.app');, the request fails with 401 Unauthorized.
I also tried to call it as getToken('https://data-provider-service-123.run.app'); and got a response 403 Forbidden. The following entry is added to data-provider-service logs for each such request
{
httpRequest: {9}insertId: "60fcb7e0000c12346fe37579"
logName: "projects/my-project/logs/run.googleapis.com%2Frequests"
receiveTimestamp: "2021-07-25T01:01:20.806589957Z"
resource: {2}
severity: "WARNING"
textPayload: "The request was not authorized to invoke this service. Read more at
https://cloud.google.com/run/docs/securing/authenticating"
timestamp: "2021-07-25T01:01:20.800918Z"
trace: "projects/my-project/traces/25b3c13890aad01234829711d4e9f25"
}
I also tried to obtain and use JWT from compute metadata server (also a way advertised in the docs) by running curl "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=http://data-provider-servive-123.run.app" \ -H "Metadata-Flavor: Google".
When I execute this code in Cloud Shell, I get JWT that looks like this
{
"iss": "accounts.google.com",
"azp": "123456789-9r9s1c4alg36erliucho9t52n12n6abc.apps.googleusercontent.com",
"aud": "123456789-9r9s1c4alg36erliucho9t52n12n6abc.apps.googleusercontent.com",
"sub": "106907196154567890477",
"email": "my_email#gmail.com",
"email_verified": true,
"at_hash": "dQpctkQE2Sy1as1qv_w",
"iat": 1627173901,
"exp": 1627177501,
"jti": "448d660269d4c7816ae3zd45wrt89sb9f166452dce"
}
Then I make a request using postman to https://data-provider-service/get-data with a header Authorization: "Bearer <the_jwt_from_above>" and everything works fine
However, if I make the same request from my api-service, the JWT returned is
{
"aud": "http://data-provider-service-123.run.app",
"azp": "111866132465987679716",
"email": "api-service-account#domain",
"email_verified": true,
"exp": 1627179383,
"iat": 1627175783,
"iss": "https://accounts.google.com",
"sub": "111866132465987679716"
}
If I make the same request with the postman and place this JWT into the Authorization header, the 401 Unauthorized is returned.
I have spent this week trying to solve this issue. I triple-checked the permissions, redeployed the services several times, but nothing helps.
I changed http to https in the URL of the target service when asking computing metadata server, and looks like it solved the problem. Requesting token like this with google-auth-library still fails with 403 error.
I'll update that answer if the solution is sustainable.

Local Keycloak setup returns "Error: unable to verify the first certificate" nodejs error

Trying to use Keycloak as SSO running within Kubernetes cluster on minikube to authenticate demo nodejs app:
var express = require('express');
var session = require('express-session');
var Keycloak = require('keycloak-connect');
var cors = require('cors');
var app = express();
app.use(cors());
const memoryStore = new session.MemoryStore();
app.use(session({
secret: 'some secret',
resave: false,
saveUninitialized: true,
store: memoryStore
}));
var keycloakConfig = {
"realm": "Demo-Realm",
"auth-server-url": "https://keycloak.192.168.49.2.nip.io/auth/",
"ssl-required": "external",
"resource": "nodejs-microservice",
"verify-token-audience": true,
"credentials": {
"secret": "14de3a01-5c15-42fd-aa6a-fcc35c0961ff"
},
"use-resource-role-mappings": true,
"confidential-port": 0,
"policy-enforcer": {}
};
const keycloak = new Keycloak( { store : memoryStore }, keycloakConfig );
app.use(keycloak.middleware({
logout: '/logout',
admin: '/'
}));
app.get('/user', keycloak.protect('user'), function(req, res){
res.send("Hello User");
});
app.listen(3000, function () {
console.log('Started at port 3000');
});
But getting Access denied page when accessing http://localhost:3000/user with NodeJS console error: Could not obtain grant code: Error: unable to verify the first certificate
I have Demo-Realm realm created in Keycloak with such settings:
nodejs-microservice client:
Access Type: confidential
Valid Redirect URIs: http://localhost:3000/*
Authorization Enabled: ON
Roles: [ 'user', 'admin' ]
No login redirects happening (basic example works properly though). What could be the problem? How can I secure my NodeJS microservice with Keycloak?
Problem is somehow related to default self-signed certificate on Keycloak side. People recommend spending some time on obtaining proper certificate.
Can be temporary solved by muting certificate verification on NodeJS side with placing such line before api/express calls:
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0;

API requests(Mantis BT) from another domain - preflight error

I'm developing a Mantis BT client in ColdFusion but I have a problem when I try to make requests from a different domain.
If I making requests from the same domain where I installed Mantis BT all work fine but when I try to make a request from a different domain or the same domain with another port(localhost:8500 - ColdFusion) the browser return "Failed to load https://localhost/mantis/api/rest/users/me: Response for preflight has invalid HTTP status code 401." error.
I have added all headers in mantis config but it still not working.
If I try to make a request with postman all work fine.
var settings = {
"async": true,
"crossDomain": true,
"url": "localhost:8080/mantis/api/rest/users/me",
"method": "GET",
"headers": {
'Access-Control-Expose-Headers': 'Access-Control-Allow-Origin',
'Access-Control-Allow-Origin': '*',
'Authorization': 'MAzzT5UD4cjxwwOayyLFAXnlIPQJmiL_'
}
}
$.ajax(settings).done(function (response) { console.log(response); });
I resolved it by enable headers module in apache.