B4J Question [BANano] [B4J] The dreaded CORS exception [Solved]

Dave G

Active Member
Licensed User
I've spent many hours trying to eliminate CORS issues to no avail. I have a browser with security disable to get around it so I can test.

Here's the exception in the browser Console:

Access to fetch at 'http://192.168.0.238:8899/hello?parms=theToken~ln~abc~abc' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

It occurs using any client (Chrome, Echo, Brave, etc.). The only thing common, in my case, is I'm using JETTY on the server side as the it's B4J. I get the same issue with Vue.js or BANano as the browser client.

Thank
 
Solution
You don't have to download anything. With the latest B4J (with jServer 4), you should have all the needed .jar files (among them jetty-jakarta-servlet-api-5.0.2.jar) in your Libraries/jserver folder. They do not require a .xml file as they are directly linked with the jServer.jar.
Thank you, thank you, thank you. I was on B4J 9.10, which obviously was older Jetty. Installed and no more CORS exceptions.

Only thing I have to figure out is why req.GetHeaders("Authorization") works when security disabled and doesn't when it's not. No biggie.

alwaysbusy

Expert
Licensed User
Longtime User
CORS is caused from the server side because it doesn't return the required HTTP headers. Every Browser will do this. If using jServer, things you can do:

1. Add a CORS filter (new class e.g. ABMCorsFilter)
B4X:
Sub Class_Globals
    Private cPath As String
    Private cSettings As Map
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize (Path As String, Settings As Map)
    cPath = Path
    cSettings = Settings
End Sub

Public Sub AddToServer (ServerObject As Server)
    Dim joServerWrapper As JavaObject = ServerObject
    Dim joMe As JavaObject = Me
    joMe.RunMethod("addFilter", Array As Object(joServerWrapper.GetField("context"), cPath, cSettings))
End Sub

#If JAVA

import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map.Entry;

import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;

import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.FilterHolder;

import anywheresoftware.b4a.objects.collections.Map.MyMap;

public void addFilter(ServletContextHandler context, String path, MyMap settings) throws Exception {
    FilterHolder fh = new FilterHolder((Class<? extends Filter>) Class.forName("org.eclipse.jetty.servlets.CrossOriginFilter"));
    if (settings != null) {
        HashMap<String,String> m = new HashMap<String, String>();
        copyMyMap(settings, m, true); //integerNumbersOnly!
        fh.setInitParameters(m);
    }
    context.addFilter(fh, path, EnumSet.of(DispatcherType.REQUEST));
}

private void copyMyMap(MyMap m, java.util.Map<String, String> o, boolean integerNumbersOnly) {
    for (Entry<Object, Object> e : m.entrySet()) {
        String value;
        if (integerNumbersOnly && e.getValue() instanceof Number) {
            value = String.valueOf(((Number)e.getValue()).longValue());
        } else {
            value = String.valueOf(e.getValue());
            o.put(String.valueOf(e.getKey()), value);
        }
    }
}

#End If

In main, make a method:
B4X:
    ' allowedOrigins = "*" or "http://google.com"
    ' allowedMethods = "*" or "GET,POST,HEAD"
    ' allowedHeaders = "*" or "X-Requested-With,Content-Type,Accept,Origin"
Private Sub ConfigureCORS (srvr As Server, Path As String, allowedOrigins As String, allowedMethods As String, allowedHeaders As String) 'ignore
    Dim cors As ABMCorsFilter
    cors.Initialize(Path, CreateMap("allowedOrigins": allowedOrigins, _
    "allowedMethods": allowedMethods, _
    "allowedHeaders": allowedHeaders, _
    "allowCredentials": "true", _
    "preflightMaxAge": 1800, _
    "chainPreflight": "false"))
    cors.AddToServer(srvr)
End Sub

Right before srvr.Start
B4X:
ConfigureCORS(srvr, "/*", "*", "*", "*") ' or whatever restrictions you want to set

2. When using e.g. a jServer REST API, add some filter like this (restrict where applicable):
B4X:
        resp.ContentType = "application/json"
        resp.SetHeader("Access-Control-Allow-Origin","*")
        resp.SetHeader("Access-Control-Allow-Methods" ,"GET, POST, UPDATE, DELETE, OPTIONS")
        resp.SetHeader("Access-Control-Allow-Headers", "Access-Control-Allow-Headers, Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Authorization, api_key")
        If req.Method = "OPTIONS" Then
            Return True
        End If
        resp.SetHeader("X-Frame-Options", "DENY")
        resp.SetHeader("X-XSS-Protection", "1;mode=block")
        resp.SetHeader("Strict-Transport-Security", "max-age=31536000;includeSubDomains;preload")
        resp.SetHeader("X-Content-Type-Options", "nosniff")
        resp.SetHeader("Referrer-Policy", "no-referrer-when-downgrade")
        resp.SetHeader("Content-Security-Policy", "script-src https://api.yourdomain.com")
        resp.SetHeader("Feature-Policy", "microphone 'none'")

3. Put your BANano projects in the B4J jServer www folder.

4. Run over https with a valid certificate

Alwaysbusy
 
Last edited:
Upvote 0

Dave G

Active Member
Licensed User
CORS is caused from the server side because it doesn't return the required HTTP headers. Every Browser will do this If using jServer, things you can do:

1. Add a CORS filter (new class e.g. ABMCorsFilter)
B4X:
Sub Class_Globals
    Private cPath As String
    Private cSettings As Map
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize (Path As String, Settings As Map)
    cPath = Path
    cSettings = Settings
End Sub

Public Sub AddToServer (ServerObject As Server)
    Dim joServerWrapper As JavaObject = ServerObject
    Dim joMe As JavaObject = Me
    joMe.RunMethod("addFilter", Array As Object(joServerWrapper.GetField("context"), cPath, cSettings))
End Sub

#If JAVA

import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map.Entry;

import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;

import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.FilterHolder;

import anywheresoftware.b4a.objects.collections.Map.MyMap;

public void addFilter(ServletContextHandler context, String path, MyMap settings) throws Exception {
    FilterHolder fh = new FilterHolder((Class<? extends Filter>) Class.forName("org.eclipse.jetty.servlets.CrossOriginFilter"));
    if (settings != null) {
        HashMap<String,String> m = new HashMap<String, String>();
        copyMyMap(settings, m, true); //integerNumbersOnly!
        fh.setInitParameters(m);
    }
    context.addFilter(fh, path, EnumSet.of(DispatcherType.REQUEST));
}

private void copyMyMap(MyMap m, java.util.Map<String, String> o, boolean integerNumbersOnly) {
    for (Entry<Object, Object> e : m.entrySet()) {
        String value;
        if (integerNumbersOnly && e.getValue() instanceof Number) {
            value = String.valueOf(((Number)e.getValue()).longValue());
        } else {
            value = String.valueOf(e.getValue());
            o.put(String.valueOf(e.getKey()), value);
        }
    }
}

#End If

In main, make a method:
B4X:
    ' allowedOrigins = "*" or "http://google.com"
    ' allowedMethods = "*" or "GET,POST,HEAD"
    ' allowedHeaders = "*" or "X-Requested-With,Content-Type,Accept,Origin"
Private Sub ConfigureCORS (srvr As Server, Path As String, allowedOrigins As String, allowedMethods As String, allowedHeaders As String) 'ignore
    Dim cors As ABMCorsFilter
    cors.Initialize(Path, CreateMap("allowedOrigins": allowedOrigins, _
    "allowedMethods": allowedMethods, _
    "allowedHeaders": allowedHeaders, _
    "allowCredentials": "true", _
    "preflightMaxAge": 1800, _
    "chainPreflight": "false"))
    cors.AddToServer(srvr)
End Sub

Right before srvr.Start
B4X:
ConfigureCORS(srvr, "/*", "*", "*", "*") ' or whatever restrictions you want to set

2. When using e.g. a jServer REST API, add some filter like this (restrict where applicable):
B4X:
        resp.ContentType = "application/json"
        resp.SetHeader("Access-Control-Allow-Origin","*")
        resp.SetHeader("Access-Control-Allow-Methods" ,"GET, POST, UPDATE, DELETE, OPTIONS")
        resp.SetHeader("Access-Control-Allow-Headers", "Access-Control-Allow-Headers, Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Authorization, api_key")
        If req.Method = "OPTIONS" Then
            Return True
        End If
        resp.SetHeader("X-Frame-Options", "DENY")
        resp.SetHeader("X-XSS-Protection", "1;mode=block")
        resp.SetHeader("Strict-Transport-Security", "max-age=31536000;includeSubDomains;preload")
        resp.SetHeader("X-Content-Type-Options", "nosniff")
        resp.SetHeader("Referrer-Policy", "no-referrer-when-downgrade")
        resp.SetHeader("Content-Security-Policy", "script-src https://api.yourdomain.com")
        resp.SetHeader("Feature-Policy", "microphone 'none'")

3. Put your BANano projects in the B4J jServer www folder.

4. Run over https with a valid certificate

Alwaysbusy
Will give it try. Thanks a bunch.
 
Upvote 0

Dave G

Active Member
Licensed User
Jakarta:
import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;

I downloaed jakarta.servlet-api-6.0.0.jar, but since it doesn't have an accompanying .xml, B4J Libraries doesn't see it!
 
Upvote 0

Dave G

Active Member
Licensed User
Well had to give up. Can't find jakarta library jar/xml, etc. so unable to create a CORS compliant server. It seems that Jetty should offer compliance. I've put in many, many hours over the past six months wrestling with this, so time to move on.
 
Upvote 0

Dave G

Active Member
Licensed User
You don't have to download anything. With the latest B4J (with jServer 4), you should have all the needed .jar files (among them jetty-jakarta-servlet-api-5.0.2.jar) in your Libraries/jserver folder. They do not require a .xml file as they are directly linked with the jServer.jar.
Thank you, thank you, thank you. I was on B4J 9.10, which obviously was older Jetty. Installed and no more CORS exceptions.

Only thing I have to figure out is why req.GetHeaders("Authorization") works when security disabled and doesn't when it's not. No biggie.
 
Upvote 0
Solution
Top