Search code examples
djangodjango-rest-frameworkjwtgrafanadjango-rest-framework-simplejwt

Make grafana use existing JWT token


I have a React + DRF web app that has JWT authentication, in which I use djangorestframework-simplejwt.

I store access and refresh tokens in the localStorage. Is it possible to use these tokens to authenticate in Grafana? If yes, how can I do that? When I navigate to /grafana (with nginx help), I would like to see that my user in my app to be logged in to the Grafana, by creating the user for Grafana if necessary.


Solution

  • Let me explain all of the details for those who seek a solution to the problem of using common JWT for their app and Grafana. You can skip the beginning if you only care about Grafana side only:

    React side:

    • I have a Django REST Framework API and React UI. When a token returned to the user, React UI saves it to the local storage. I implemented an onClick handler for a button to navigate to Grafana like this:
    // read token value from local storage
    const refToken = localStorage.getItem("refresh_token");
    window.location.href = `/grafana/login/?mytoken=${refToken}`;
    

    Django REST Framework side (djangorestframework-simplejwt):

    • The API can generate and validate JWT tokens using RS256 signing algorithm. Because of the RSA, the API needs to generate 2 keys, private and public key. I have generated those with jwcrypto. I set SIGNING_KEY with the contents of the private key .pem and set VERIFYING_KEY with the contents of the public key .pem.
    from jwcrypto import jwk
    import uuid
    
    keyid = str(uuid.uuid4())
    key = jwk.JWK.generate(kty='RSA', alg='RS256', size=2048, kid=keyid, use='sig')
    
    # export to PEM files
    priv_pem = key.export_to_pem(private_key=True, password=None)
    pub_pem = key.export_to_pem()
    
    with open("rsa_pub.pem", "wb") as f:
        f.write(pub_pem)
    
    with open("rsa.pem", "wb") as f:
        f.write(priv_pem)
    
    SIMPLE_JWT = {
        'ACCESS_TOKEN_LIFETIME': timedelta(hours=1),
        'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
        'SIGNING_KEY': open("/path/to/folder/which/has/keys/rsa.pem").read(),
        'VERIFYING_KEY': open("/path/to/folder/which/has/keys/rsa_pub.pem").read(),
        'ALGORITHM': 'RS256',
        'USER_ID_FIELD': 'username',
        'USER_ID_CLAIM': 'username',
        'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    }
    

    Grafana side:

    • To make sure that this is working, you need to create users with same username for the Grafana. Otherwise you will get Invalid JWT response. auto_sign_up setting is not working for the JWT authentication yet.

    • Then I added these configurations for Grafana container. header_name can be any string and but you need to use it in nginx side too:

    version: "3"
    services:
      ...
      ...
      grafana:
        image:  grafana/grafana:8.2.2
        volumes:
          - ...
          - "/path/to/folder/which/has/keys:/key_set"
        environment:
          - "GF_SERVER_ROOT_URL=/grafana/"
          - "GF_SERVER_SERVE_FROM_SUB_PATH=true"
          - "GF_AUTH_PROXY_ENABLED=true"
          - "GF_AUTH_PROXY_ENABLE_LOGIN_TOKEN=true"
          - "GF_AUTH_JWT_ENABLED=true"
          - "GF_AUTH_JWT_HEADER_NAME=X-JWT-Assertion"
          - "GF_AUTH_JWT_USERNAME_CLAIM=username"
          - "GF_AUTH_JWT_KEY_FILE=/key_set/rsa_pub.pem"
    

    Nginx side:

    location /grafana/ {
        try_files /dev/null @proxy_grafana;
    }
    
    location /grafana/login/ {
        try_files /dev/null @proxy_grafana_login;
    }
    
    location @proxy_grafana {
        ...
        proxy_pass   http://grafana:3000;
    }
    
    location @proxy_grafana_login {
        ...
        proxy_set_header X-JWT-Assertion "${arg_mytoken}";
        proxy_pass   http://grafana:3000;
    }