CORS Problem - Protecting ReactJS Frontend and JAX-RS Backend with Keycloak

Viewed 534

I am a Junior Software Developer from Germany and I am currently trying to setup some Webservices from a Jax-RS Rest API (deployed on a Wildfly Server) that are protected by „Keycloak“ and accessible through a simple react app.

I have done all the exact steps from this article: https://medium.com/devops-dudes/secure-front-end-react-js-and-back-end-node-js-express-rest-api-with-keycloak-daf159f0a94e.

The only differences are the the following:

  • I have three services: „Level Zero“, „Level One“ and „Level Two“.
  • Every Service simply returns a string (Example: "This is the Service Level One")
  • I have two Roles defined in Keycloak: „Level One User“ and „Level Two User“
  • The Level Two User shall be auhtorized to access all the services
  • The Level One User shall only be authorized to access the services Level One and Level Zero
  • every other User shall only be able to access the service Level Zero

In my React App I have three Buttons that shall access a service via the framework "axios". You click on a Button, and the returned string shall be logged out in the console if you are authorized to do so.

My Problem: If I run my app, I always get a CORS Error in my Webconsole when I try to access "LevelOne" or "LevelTwo" as an authenticatd Level One or Level Two User. This problem does not exist with the Level Zero service that is not protected by Keycloak.

My (translated) Error: Cross Origin Request Blocked: Same Origin Policy does not allow Read-Access on the external resource URL_FROM_API_SERVICE - REASON: CORS Header 'Access-Control-Allow-Origin" is missing!

I tried a lot of stuff that I have found online --> I created a CORS-Filter in my Rest API, I tried to put "enable-cors:true" in my keycloak.json, I put something in the "web-origins" field in my Keycloak client configuration. But nothing worked. :(

What am I doing wrong? I really need your help! I am quite new to all of this and would really appreciate some support.

My Keycloak Configuration is the same as shown in the article, except for the different names.

It also did not help to add "enable-cors:true" in the keycloak.json and setting the "web-origin" to the right origin on the Keycloak Admin Console :(

The API and the React App run on HTTP at the moment, while Keycloak runs on HTTPS on another machine with a self signed certificate.

Here is all my Code:

My App.js from the React App:

import './App.css';
import Secured from './components/Secured.js'
import axios from 'axios';

var axiosInstance = axios.create({
    baseURL: 'http://MY_BASE_URL/login-restapi/api/'
});

axiosInstance.interceptors.request.use(
    config => {
      const token = window.accessToken ? window.accessToken : 'dummy_token';
      config.headers['Authorization'] = 'Bearer' + token;
      return config;
    },
    error => {
      Promise.reject(error)
    });

axiosInstance.interceptors.response.use((response) => {
  return response
}, function (error) {
  return Promise.reject(error);
});

function App() {

  return (
    <div className="App">
      <Secured></Secured>
        <button onClick={() => {
            axiosInstance.get('/levelZero').then(res => {
                console.log(res.data)
            })
        }
        }>LevelZero</button>
        <button onClick={() => {
            axiosInstance.get('/levelOne').then(res => {
                console.log(res.data)

            })
        }
        }>LevelOne</button>
        <button onClick={() => {
            axiosInstance.get('/levelTwo').then(res => {
                console.log(res.data)
            })
        }
        }>LevelTwo</button>
    </div>
  );
}

export default App;

My Secured.js from the React App:

import React, { Component } from 'react';
import Keycloak from 'keycloak-js';


class Secured extends Component {

    constructor(props) {
        super(props);
        this.state = { keycloak: null, authenticated: false };
    }



    componentDidMount() {
        const keycloak = Keycloak('/keycloak.json');
        keycloak.init({ onLoad: 'login-required' }).then(authenticated => {
            this.setState({ keycloak: keycloak, authenticated: true})
            if (authenticated) {
                window.accessToken = keycloak.token;
            }
        })
    }

    render(){
        if (this.state.keycloak) {
            if(this.state.authenticated) return (
                <div>
                    <p>You are now logged in :)</p>
                </div>
            ); else return (<div>Unable to authenticate!</div>)
        }
        return (
            <div>Initializing Keycloak...</div>
        );
    }
}

export default Secured;

My Keycloak.json from the react app:

{
  "realm": "(MY_REALM_NAME)",
  "auth-server-url": "MY_AUTHSERVER_URL",
  "ssl-required": "none",
  "resource": "react-web-app",
  "public-client": true,
  "verify-token-audience": true,
  "use-resource-role-mappings": true,
  "confidential-port": 0
}

My Level Zero Service:

@Path("/levelZero")
public class LevelZeroResource {
    @GET
    @Produces("text/plain")
    public String levelZero() {
        return "Everybody can access this.";
    }
}

My Level One Service:

@Path("/levelOne")
public class LevelOneResource {
    @GET
    @Produces("text/plain")
    public String levelOne() {
        return "You need to be at least a Level One User to access this.";
    }
}

My Level Two Service:

@Path("/levelTwo")
public class LevelTwoResource {
    @GET
    @Produces("text/plain")
    public String levelTwo() {
        return "You need to be a LevelTwo-user to access this.";
    }
}

My CORS Filter from the Rest API:

@Provider
 public class CORSFilter implements ContainerResponseFilter {

    @Override
    public void filter(final ContainerRequestContext requestContext,
                       final ContainerResponseContext cres) throws IOException {
        cres.getHeaders().add("Access-Control-Allow-Origin", "http://URL_FROM_REACT_APP");
        cres.getHeaders().add("Access-Control-Allow-Headers", "*");
        cres.getHeaders().add("Access-Control-Allow-Credentials", "*");
        cres.getHeaders().add("Access-Control-Allow-Methods", "*");
        cres.getHeaders().add("Access-Control-Max-Age", "1209600");
    }

}

My Keycloak.json from the Rest API:

{
  "realm": "MY_REALM_NAME",
  "bearer-only": true,
  "enable-cors": true,
  "auth-server-url": "https://MY_AUTH_SERVER_URL",
  "ssl-required": "none",
  "resource": "login-restapi",
  "verify-token-audience": true,
  "use-resource-role-mappings": true,
  "confidential-port": 0
}

My web.xml from the Rest API:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <module-name>login-restapi</module-name>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>LevelOneResource</web-resource-name>
            <url-pattern>/api/levelOne</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>levelOneRole</role-name>
        </auth-constraint>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>LevelTwoResource</web-resource-name>
            <url-pattern>/api/levelTwo</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>levelTwoRole</role-name>
        </auth-constraint>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>

    <login-config>
        <auth-method>KEYCLOAK</auth-method>
        <realm-name>MY_REALM_NAME</realm-name>
    </login-config>

    <security-role>
        <role-name>levelOneRole</role-name>
    </security-role>
    <security-role>
        <role-name>levelTwoRole</role-name>
    </security-role>

</web-app>
2 Answers

You should not need to add a custom filter. Simply set enable-cors to true (as you did) and make sure that the Web origins are correctly configured within Keycloak. You need to add the origin of your react frontend to the list of Web origins in your client configuration in Keycloak.

enter image description here

Keycloak will then encode these origins as valid origins inside of your access token. The Keycloak adapter in your JAX-RS application will interpret them and return the Access-Control-Allow-Origin header as needed.

I saw you have enabled the cors in your keycloak config file "enable-cors: true" so make sure the web origins fields on the keycloak (as shown by @sventorben) have the backend url too.

Because even if keycloak-js authenticates the user the backend does the authorization which means talks to keycloak too.

Related