Reputation: 310
Whenever I try to send a POST request from background.js
of my Chrome extension I get 403 Forbidden
error. If I execute the same code outside my Chrome extension it works normally. My API doesn't require any authentication.
Request code:
let formData = new FormData();
formData.append('message', "This is a test message.");
fetch('https://myapi.com/add', {
body: formData,
method: "post"
}).then(r => console.log(r));
Request response:
I also checked my Apache 2 access.log
and everything seems to look normal:
x.x.x.x - - [13/Sep/2020:17:02:30 +0000] "POST add HTTP/1.1" 403 581 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36"
Are there any Chrome extension policies that could block my request?
Do I need to add any special permissions to my manifest.json
?
Do I need to make any changes to my API (take this as a reserve, because it works normally outside Chrome extension)?
Upvotes: 3
Views: 4601
Reputation: 21
I just had the same problem. Thanks so much for posting this. I also work on a Chrome extension with Django backend.
For me deleting 'rest_framework.authentication.SessionAuthentication' from the 'DEFAULT_AUTHENTICATION_CLASSES' did it, but I kept the 'oauth2_provider.contrib.rest_framework.OAuth2Authentication'.
According to this medium article "rest_framework.authenication.SessionAuthentication' is only needed if you want to keep browsable API. I have CSRF and CORS enabled. So I think my setup is very secure.
Below is my settings.py file.
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '*xbmoy2yt4%l=od-dm*w$dxpl+rb(n#rmv0n&0x$a@+io!j+++'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'audio.apps.AudioConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'accounts.apps.AccountsConfig',
'rest_framework',
'oauth2_provider',
#'corsheaders',
]
MIDDLEWARE = [
#'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'oauth2_provider.middleware.OAuth2TokenMiddleware'
]
ROOT_URLCONF = 'zeno.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'zeno.wsgi.application'
CORS_ORIGIN_ALLOW_ALL = True
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
CORS_ORIGIN_ALLOW_ALL = False
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = '/static/'
LOGIN_REDIRECT_URL = 'home'
LOGOUT_REDIRECT_URL = 'home'
PROJECT_ROOT = os.path.normpath(os.path.dirname(__file__))
STATICFILES_DIRS = (
os.path.join(PROJECT_ROOT, '..', 'static'),
)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
#'rest_framework.authentication.SessionAuthentication', # To keep the Browsable API
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
# 'DEFAULT_PAGINATION_CLASS': (
# 'rest_framework.pagination.PageNumberPagination',
# ),
# 'DEFAULT_PERMISSION_CLASSES': (
# 'rest_framework.permissions.IsAuthenticated',
# ),
#'PAGE_SIZE': 10
}
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # To keep the Browsable API
'oauth2_provider.backends.OAuth2Backend',
)
#STATIC_ROOT = os.path.join(BASE_DIR, "static/")
ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'test.com']
AUTH_USER_MODEL = 'accounts.CustomUser'
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
#EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
#EMAIL_FILE_PATH = os.path.join(BASE_DIR, "sent_emails")
Upvotes: 0
Reputation: 310
The problem was in Django REST framework. Apparently it has a few authentication classes enabled by default. Disabling/setting them to an empty array in settings.py
will get rid of the annoying '403 Forbidden' error.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
]
}
EDIT: I don't know how safe this is, but at least it works.
Upvotes: 0
Reputation: 28440
Edit: Read your server logs. The server is probably telling you exactly what the error is. Make sure debugging is enabled.
403 errors are not really emitted from a server without good reason - and that reason is usually specific to your application. A 403 is generally a permission issue: we know who you are, but you're not authorized to do what you're trying to do.
There might be a header present (or missing) which is preventing the request from making any changes (in your case, a POST request). For example, your server might be setting cookies to prevent CSRF attacks. Many times the server will not validate tokens on GET requests, which may explain why GET requests work in your case but not POST requests.
If you can search your server code, I'm willing to be there is something like this pseudocode:
if (requestIsMissingCSRFToken()) {
throw new Error(STATUS.FORBIDDEN)
}
Edit: Some relevant links
Upvotes: 1