pythonnode.jscookiescorsmicroservices

CORS, transmitting JWT via cookie using HTTPOnly


Problem: When I send a JWT token via cookies directly to http://localhost:8000/api/profile, it works fine, and I receive user data. However, when I pass the token via a second microservice, I get a 401 Unauthorized error with the message Token is missing.

Logs from the second microservice:

{
  "status": 401,
  "statusText": "Unauthorized",
  "headers": {
    "date": "Sat, 22 Feb 2025 08:03:25 GMT",
    "server": "uvicorn",
    "content-length": "29",
    "content-type": "application/json"
  },
  "data": {
    "detail": "Token is missing"
  }
}

CORS settings for the microservice sending the token:

app.set('trust proxy', true);
app.use(cookieParser());

// CORS settings
app.use(cors({
  origin: 'http://localhost:5173',
  credentials: true,
  optionsSuccessStatus: 200
}));

CORS settings for the microservice receiving the token:

app = FastAPI()

origins = [
    "http://localhost:5173"
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Code for sending cookies:

const axios = require('axios');

const getUserProfile = async (token) => {
  try {
    const response = await axios.get('http://fast-api:8000/api/profile', {
      withCredentials: true,  // Send cookies with the request
    });
    return response.data;
  } catch (error) {
    console.error('Error fetching user profile:', error);

    if (error.response) {
      console.error('Response error data:', error.response.data);
      console.error('Response error status:', error.response.status);
      console.error('Response error headers:', error.response.headers);
    } else if (error.request) {
      console.error('Request error data:', error.request);
    } else {
      console.error('General error message:', error.message);
    }

    throw new Error('Failed to fetch user profile');
  }
};

Authorization Middleware:

const authMiddleware = async (req, res, next) => {
  const token = req.cookies.access_token;

  console.log('Cookies:', req.cookies);
  console.log('Received token:', token);

  if (!token) {
    return res.status(401).json({message: 'Token is missing'});
  }

  try {
    const userProfile = await getUserProfile(token);
    req.user = userProfile;
    next();
  } catch (error) {
    console.error('Error in authMiddleware:', error);
    return res.status(401).json({
      message: error.message,
      stack: error.stack,
    });
  }
};

Code for receiving the token in FastAPI:

@router.get("/profile")
def get_user(request: Request):
    print("Received headers:", request.headers)
    print("Received query parameters:", request.query_params)
    print("Received cookies:", request.cookies)

    token = request.cookies.get("access_token")
    print("Received token:", token)
    if not token:
        raise HTTPException(status_code=401, detail="Token is missing")

    return get_user_service(token)

Issue: The logs from the sending microservice show that the cookies are being received correctly, but when they are processed by the receiving microservice, the token is not found and a 401 Unauthorized error is returned.

What could be the issue with sending the token between microservices in this way? Why is the token not being passed correctly in the request to the second microservice?


Solution

  • It sounds like you are suspecting that CORS is the root cause of the issue. I doubt that's the case. The same-origin policy and CORS only apply to requests sent from a browser. They do not apply to requests sent from one backend microservice to another one.

    Looking at your getUserProfile function, you are using axios's withCredentials option to attach the JWT cookie to the request you are sending from your first microservice to the second one:

    const response = await axios.get('http://fast-api:8000/api/profile', {
      withCredentials: true,  // Send cookies with the request
    });
    

    withCredentials does indeed control whether cookies are attached to cross-origin requests, but this option is designed to be used in a browser. It does not have any effect when sending a request from a Node.js backend service.

    Try attaching your JWT explicitly to the profile request as a Cookie header. For example:

    const getUserProfile = async (token) => {
      try {
        const response = await axios.get('http://fast-api:8000/api/profile', {
          headers: {
            Cookie: `access_token=${token}`
          }
        });
        return response.data;
      } catch (error) {
        // ...
      }
    };