I'm trying to hit my own endpoint on a subdomain controlled by Nginx, I expect the request to fail and return a JSON payload like this:
{
"hasError": true,
"data": null,
"error": {
"statusCode": 401,
"statusDescription": null,
"message": "Could not find Session Reference in Request Headers"
}
}
When I make this request in a browser, it returns a 401 with this in the network tools (Brave Browser):
And this error in the console:
Access to fetch at 'https://services.mfwebdev.net/api/authentication/validate-session' from origin 'https://mfwebdev.net' 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.
When I hit the URL in question in the browser, I see the correct JSON response, if I hit the URL in a REST client like insomnia, I can see the JSON response.
The headers that browser is sending are:
:authority: services.mfwebdev.net
:method: GET
:path: /api/authentication/validate-session
:scheme: https
accept: application/json, text/plain, */*
accept-encoding: gzip, deflate, br
accept-language: en-GB,en-US;q=0.9,en;q=0.8
origin: https://mfwebdev.net
referer: https://mfwebdev.net/
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: same-site
sec-gpc: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36
I've actually used these headers in the REST client as well and I can still see the correct JSON result.
The request (in code) is as follows (Using Angular):
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { ApiResponse } from 'src/app/api/types/response/api-response.class';
import { ISessionApiService } from 'src/app/api/types/session/session-api-service.interface';
import { SessionResponse } from 'src/app/api/types/session/session-response.class';
import { environment } from '../../../environments/environment';
@Injectable()
export class SessionApiService implements ISessionApiService {
private readonly _http: HttpClient;
constructor(http: HttpClient) {
this._http = http;
}
public createSession(): Observable<ApiResponse<SessionResponse>> {
return this._http.post<ApiResponse<SessionResponse>>(`${environment.servicesApiUrl}/authentication/authorise`, {
reference: environment.applicationReference,
applicationName: environment.applicationName,
referrer: environment.applicationReferrer
});
}
public validateSession(): Observable<ApiResponse<boolean>> {
return this._http.get<ApiResponse<boolean>>(`${environment.servicesApiUrl}/authentication/validate-session`);
}
}
Could someone please help, I'm at a complete loss here.
EDIT!! For anyone using NginX who may come across this problem. The issue was in my nginx.conf
file. I am leaving an example of my(now working) server-side configuration.
The reason it wasn't working was because I was not bothering to actually handle the request if an OPTIONS request came through.
I now handle every request type (or will) and append the ACCESS-CONTROL-ALLOW-ORIGIN
header to the request.
user root;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {}
http {
include /etc/nginx/proxy.conf;
limit_req_zone $binary_remote_addr zone=one:10m rate=5r/s;
server_tokens off;
sendfile on;
# Adjust keepalive_timeout to the lowest possible value that makes sense
# for your use case.
keepalive_timeout 1000;
client_body_timeout 1000;
client_header_timeout 10;
send_timeout 10;
upstream upstreamExample{
server 127.0.0.1:5001;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.net *.example.net;
ssl_certificate /etc/letsencrypt/live/example.net/cert.pem;
ssl_certificate_key /etc/letsencrypt/live/example.net/privkey.pem;
ssl_session_timeout 1d;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling off;
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' '*';
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
add_header 'Access-Control-Allow-Headers' '*';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
add_header 'Access-Control-Allow-Headers' '*';
}
if ($request_method = 'DELETE') {
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
add_header 'Access-Control-Allow-Headers' '*';
}
proxy_pass https://upstreamExample;
limit_req zone=one burst=10 nodelay;
}
}
}