Wednesday, 15 February 2023

fastapi: url_for in Jinja2 template do not work with https

Everything was running fine until I switched the application to use https. All the links that the url_for function generates in the templates now look like this https://ibb.co/N3cJ9V4

Problem:

Mixed Content: The page at 'https://team-mate.app/' was loaded over HTTPS, but requested an insecure stylesheet 'http://team-mate.app/static/css/materialize.min.css'. This request has been blocked; the content must be served over HTTPS.

I've seen similar problems on stackoverflow and tried every possible option, but nothing worked.

I tried the advice I found and ran Uvicorn through --proxy-headers

Dockerfile ....

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers"]

docker-compose.yaml

....

command: sh -c "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000 --reload --proxy-headers"

But nothing has changed. The problem remains. Maybe I misunderstood the advice or the nginx config needs to be edited somehow.

The second way I used

from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
app = FastAPI()
app.add_middleware(HTTPSRedirectMiddleware)

But got cyclic redirect HTTP/1.0" 307 Temporary Redirect for all my urls`

Also tried this No effect

My current configs

nginx-config

server {
    listen 80;
    server_name team-mate.app;
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
    location / {
        return 301 https://$host$request_uri;
    }
}
server {
    listen 443 ssl;
    server_name team-mate.app;
    server_tokens off;

    location /static/ {
        gzip            on;
        gzip_buffers    8 256k;
        root /app;
    }

    ssl_certificate /etc/letsencrypt/live/team-mate.app/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/team-mate.app/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass http://web:8000;
        proxy_set_header    Host                $host;
        proxy_set_header    X-Real-IP           $remote_addr;
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
    }

    location /favicon.ico {
        access_log off;
        log_not_found off;
    }

}

docker-compose.yaml

version: '3.9'

services:
  web:
    env_file: .env
    build: .
    command: sh -c "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000 --reload"
    volumes:
      - .:/app
    ports:
      - 8000:8000
    depends_on:
      - db
      - redis
  db:
    image: postgres:11
    volumes:
      - postgres_data:/var/lib/postgresql/data
    expose:
      - 5432
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASS}
      - POSTGRES_DB=${DB_NAME}
  redis:
    image: redis:6-alpine
    volumes:
      - redis_data:/data
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
    volumes:
      - ./nginx_config.conf:/etc/nginx/conf.d/default.conf
      - ./data/nginx:/etc/nginx/conf.d
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    depends_on:
      - web
  certbot:
    image: certbot/certbot
    volumes:
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

volumes:
  postgres_data:
  redis_data:

main.py

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")

views.py

@router.get('/', response_class=HTMLResponse)
async def main_page(request: Request,
                    user: User = Depends(UserService.get_authenticated_user_id)
                    ):
    return templates.TemplateResponse('base.html',
                                      context={
                                          'request': request,
                                          'user': user,
                                      }
                                      )

html

  <link type="text/css" href="" rel="stylesheet">
  <link type="text/css" href="" rel="stylesheet">

p.s.

I followed MatsLindh's advice and added the proxy_set_header X-Forwarded-Proto $scheme parameter to the nginx configuration

And added --forwarded-allow-ips="*" to docker-compose file as

command: sh -c "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000 --reload --proxy-headers --forwarded-allow-ips="*""

And it made a bit of a difference - the debugger stopped writing Mixed-content but returns 404 error for static files

404

upd2

In the end, after many attempts to solve the problem, I managed to convert the application to work with https. Now the function url_for e.g. href="" is correctly executed and redirects to the correct URL. Also, the warning in the browser that the connection is not secure is gone.

The trick was to use --proxy-headers --forwarded-allow-ips inside docker-compose

command: sh -c "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000 --reload --proxy-headers --forwarded-allow-ips="*""

and X-Forwarded-Proto in nginx config

proxy_set_header    X-Forwarded-Proto   $scheme;

There is one problem left to solve. I still get 404 for the static folder. Therefore, the styles don't work.

My current nginx-config

server {
    listen 80;
    server_name team-mate.app;
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
    location / {
        return 301 https://$host$request_uri;
    }
}
server {
    listen 443 ssl;
    server_name team-mate.app;
    server_tokens off;

    location /static/ {
        gzip            on;
        gzip_buffers    8 256k;
        alias /app/static/;
    }

    ssl_certificate /etc/letsencrypt/live/team-mate.app/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/team-mate.app/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass http://web:8000;
        proxy_set_header    Host                $host;
        proxy_set_header    X-Real-IP           $remote_addr;
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto   $scheme;
    }

    location /favicon.ico {
        access_log off;
        log_not_found off;
    }

}

and docker-compose.yaml

version: '3.9'

services:
  web:
    env_file: .env
    build: .
    command: sh -c "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000 --reload --proxy-headers --forwarded-allow-ips="*""
    volumes:
      - .:/app
    ports:
      - 8000:8000
    depends_on:
      - db
      - redis
  db:
    image: postgres:11
    volumes:
      - postgres_data:/var/lib/postgresql/data
    expose:
      - 5432
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASS}
      - POSTGRES_DB=${DB_NAME}
  redis:
    image: redis:6-alpine
    volumes:
      - redis_data:/data
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
    volumes:
      - ./nginx_config.conf:/etc/nginx/conf.d/default.conf
      - ./data/nginx:/etc/nginx/conf.d
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    depends_on:
      - web
  certbot:
    image: certbot/certbot
    volumes:
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

volumes:
  postgres_data:
  redis_data:

The interesting thing is that even if I explicitly specify the path to the statics in the templates

<link rel="stylesheet" href="https://team-mate.app/static/css/custom.css'">

it still returns a 404.



from fastapi: url_for in Jinja2 template do not work with https

No comments:

Post a Comment