Save User Session & referrer to it within Dash & Django

I’ve been racking my head around this problem for some time. My application is structured like so: Dash as the frontend, Django as the backend with a Ninja API as a connection between the two.

Where I currently sit, I’ve setup a relationship between Django and Dash. I’ve been able to create a login form via dash that logins the user via Django from an API call that asks for username and password and returns:

{'username': 'pip', 'email': 'pipinstallpython@gmail.com', 'id': 1, 'logged_in': True, 'time_logged_in': '2022-12-31 02:44:43', 'signed_obj': 'eyJ1c2VybmFtZSI6InBpcGluc3RhbGxweXRob25AZ21haWwuY29tIiwiZW1haWwiOiJwaXBpbnN0YWxscHl0aG9uQGdtYWlsLmNvbSIsImlkIjoxLCJsb2dnZWRfaW4iOnRydWUsInRpbWVfbG9nZ2VkX2luIjoiMjAyMi0xMi0zMSAwMjo0NDo0MyJ9:kc70v44MKr6S_CkDOttoJCx1X5os-WJqK1Isc464fYo'}

Then in my app.py I need to save this globally on user state so that I can update app depending if a user is logged in or not, not sure how to do this. Currently I can change the page the user logged into but as soon as they get redirected to another page the whole app refreshes to its initial app state.

I’ve tried a few different approaches, first idea was to use store. I’ve tried creating a cache on login that saves the values locally (it does) currently saving it withing cache/user/{signed_obj} but didn’t understand how to carry the value into the app to keep the user logged in.

Then I tried sessions and have been able to create and store the values in sessions with the help of:

from dash_labs.session import session, setup_sessions
from dash_labs.session.backends.diskcache import DiskcacheSessionBackend

Was able to save it to a session.user and retrieve said session in the login @callback but wasn’t able to find the session in an individual pages/page.py.

Kinda lost and feel like I’m not going to have much hair left if I keep running around in circles lol This is my project layout:

With Django and plotly be independent of each other I don’t think this is a Django problem, but more of an issue with creating the last steps in a user state and saving it to a global variable or something that is related to user instance of the application. Also not sure out of the 4 approaches outlined what is the correct path to move forward? Cache, store, sessions or something else that I might be missing?

Anyone who has experience in this type of stuff please reach out, have a github I can invite those interested in helping or providing context. Feel like most of my code is decently setup and explained but this feels like the last lego and I need to find/truly understand this concept of creating something thats capable of keeping the user logged in throughout the diffrent pages and keeps a specific component set a certain way depending if user is authorized or annonymous.

Regards & thanks ahead for any feedback,
-pip

One of the reasons django-plotly-dash was written was to take advantage of the user and session management from Django. There is quite a lot of ground one has to cover and using an existing framework saves a lot of time.

However it works by embedding the Dash endpoints within a Django app, which might be a non-trivial change given how far you’ve already progressed.

1 Like


@delsim My current goal is to decuple django-plotly-dash, this diagram should help at doing my project justice.

The problem I have with django-plotly-dash is the speed and I really see dash as being able to scale better as a separate entity with pages:

For example believe django-plotly-dash is running the dash app in a way that causes lag and I’m trying to keep dash a quick independent hosted frontend framework:

[Django->[<~[[Postgres]-> [Session]->Dash->html render]]

where I’m trying to setup:
[channels->app.py]<-~- <(0.0)> ~-~>[API<-Django<~>Postgres]

The only issue I’m currently facing with this setup is establishing a save of the validators I get returned from django, to be saved to a location within dash where the app keeps a fixed key, value storage. think Logged in = True or cookies → Sent to dash → Saved in sessions → if cookie in sessions → return logged in navbar else retun default navbar type of problem.

This is what I was able to build on the Django side of things is a basicAuth to manage the API Request and Login user authentication (working):

api = NinjaAPI()


class BasicAuth(HttpBasicAuth):
    def authenticate(self, request, username, password):

        # Request all Users on Application
        users = User.objects.all()
        # check username vs user in database
        user_in_users = users.values_list('username', flat=True)
        # print('user_in_users', list(user_in_users))

        emails = users.values_list('email', flat=True)
        # print('emails', list(emails))

        # check if username or email in database return login or none
        if username in list(user_in_users):
            user = auth_authenticate(username=username, password=password)
            if user is not None:

                def cookie_save_user_id_function(request, current_time):
                    signer = Signer()
                    user_id = User.objects.get(username=username).id
                    signed_obj = signer.sign_object({'username': username, 'email': user.email,'id': user_id, 'logged_in': True, 'time_logged_in': current_time})
                    return {'username': username, 'email': user.email,'id': user_id, 'logged_in': True, 'time_logged_in': current_time, 'signed_obj': signed_obj}

                user = authenticate(request, username=username, password=password)
                if user is not None:
                    # login user
                    login(request, user)
                    central_time = pytz.timezone('US/Central')
                    current_time = datetime.now(central_time)
                    current_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
                    cookie = cookie_save_user_id_function(request, current_time)

                    # grab httpresponse
                    response = HttpResponse('You are logged in')
                    # set cookie
                    response.set_cookie('cookie', cookie['signed_obj'])


                    print(Color.GREEN+'Was able to login user, saved cookie of user_id to logged in', cookie)
                    print(Color.CYAN + 'User:', user, Color.RESET + f'Just logged into the application at: {current_time}')
                    # the credentials are valid
                    return cookie
                else:
                    print(Color.RED +'User was found but not able to authenticate on django')
                    # the credentials are invalid
                    return None
            else:
                # the credentials are invalid
                return None
        elif username in list(emails):
            print(Color.YELLOW + 'Wasn\'t able to find Username, trying email')
            print(Color.GREEN + 'Email in database')
            # gets username from email
            u = User.objects.get(email=username)

            def cookie_save_user_id_function(request, current_time):
                signer = Signer()
                user_id = User.objects.get(email=username).id
                signed_obj = signer.sign_object(
                    {'username': username, 'email': user.email, 'id': user_id, 'logged_in': True,
                     'time_logged_in': current_time})



                return {'username': u.username ,'email': username, 'id': user_id, 'logged_in': True, 'time_logged_in': current_time, 'signed_obj': signed_obj}


            # checks authentication but doesn't login
            # user = auth_authenticate(username=u, password=password)
            # authenticate
            user = authenticate(request, username=u, password=password)
            if user is not None:
                print('testing username')
                print(u.username)
                # login user
                login(request, user)
                central_time = pytz.timezone('US/Central')
                current_time = datetime.now(central_time)
                current_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
                cookie = cookie_save_user_id_function(request, current_time)

                # grab httpresponse
                response = HttpResponse('You are logged in')
                # set cookie
                response.set_cookie('cookie', cookie['signed_obj'])

                print(Color.GREEN + 'Was able to login user, saved cookie of user_id to logged in', cookie['signed_obj'])
                print(Color.CYAN + 'User:', user, Color.RESET + f'Just logged into the application at: {current_time}')
                # the credentials are valid
                return cookie
            else:
                print(Color.RED + 'Username / Email not in database')
                # the credentials are invalid
                return None

This just checks username & password in the Django database and returns either dic or None. Then a data file in dash hosts a request that provides users username & password from a forum on dash side of the application that looks like this (working):

import requests
import colorama

def login(username, password):

    # point to django domain for api login
    url_login = f'http://{username}:{password}@127.0.0.1:8000/api/login' 

    response = requests.get(url_login, auth=(username, password))

    if response.status_code == 200:
        print(colorama.Fore.GREEN + f"Login Successful: {response.json()['httpuser']}")
        print(colorama.Fore.YELLOW + f'{response.headers}')

        print(colorama.Fore.RESET)
        print(response.json())
        return response.json()
    else:
        print(colorama.Fore.RED + f"Login Failed: {username}")
        print(colorama.Fore.RESET)
        return False

if __name__ == '__main__':
    # login('pip@gmail.com', 'NeverTellMeTheOdds')
    # login('pip', 'NeverTellMeTheOdds')

Then in my dash app.py file, I have the initial setup:

from flask import Flask
from uuid import uuid4
import dash
from dash.dependencies import Output, Input, State, DiskcacheManager
from dash_labs.session import session, setup_sessions
from dash_labs.session.backends.diskcache import DiskcacheSessionBackend
import dash_mantine_components as dmc
import dash_bootstrap_components as dbc
from dash_iconify import DashIconify
import diskcache

server = Flask(__name__)

launch_uid = uuid4()

app = Dash(
    __name__,
    assets_url_path="assets",
    external_stylesheets=[
        "https://use.fontawesome.com/releases/v6.2.1/css/all.css",
        dbc.themes.SKETCHY,
    ],
    use_pages=True,
    server=server,
)

setup_sessions( app,  DiskcacheSessionBackend(directory='./session-cache'))

.....html layout components for body.....

login_form = dbc.Form(
    [
        dbc.Col(
            [
                dmc.Stack(
                    children=[
                        dmc.TextInput(
                            label="Your Username or Email:",
                            style={"width": "100%"},
                            id="login-username",
                        ),
                    ],
                )
            ]
        ),
        dbc.Col(
            [
                dmc.PasswordInput(
                    label="Your password:",
                    style={"width": "100%"},
                    placeholder="Your password",
                    icon=DashIconify(icon="bi:shield-lock"),
                    id="login-password",
                )
            ]
        ),
    ],
    className="mb-5",
)
login_modal = dmc.Card(
    children=[
        dmc.CardSection(
            dmc.Image(
                src=dash.get_asset_url("gif/login_banner.gif"),
                height=160,
            )
        ),
        dmc.Group(
            [
                dmc.Text("Access Account", weight=500),
            ],
            position="apart",
            mt="md",
            mb="xs",
        ),
        login_form,
        dmc.Button(
            "Login",
            variant="light",
            color="blue",
            fullWidth=True,
            mt="md",
            radius="md",
            id="login-button",
        ),
    ],
    withBorder=True,
    shadow="sm",
    radius="md",
    style={"width": 350},
    id="login-modal-form",
)


login_to_account = dmc.Modal(
    children=[
        html.Div(id="welcome-back-alert"),
        html.Div(id="User-Avatar"),
        login_modal,
    ],
    id="login-account-modal",
    overflow="outside",
    opened=False,
    size="sm",
)

layout=html.Div([login_to_accoun, dmc.Button(
            "Login",
            variant="light",
            color="blue",
            fullWidth=True,
            mt="md",
            radius="md",
            id="login-button",
        ),])

Lastly I setup a @app.callback to manage the adding user into a dash-hosted variable that will carry over multiple pages (NOT WORKING):

# Store Data through Callbacks
@app.callback(
    # Output("store", "data"), hiding the modal on success
    [
        Output("welcome-back-alert", "children"),
        Output("login-modal-form", "hidden"),
        Output("User-Avatar", "children"),
    ],
    [
        Input("login-button", "n_clicks"),
        Input("login-username", "value"),
        Input("login-password", "value"),
    ],
    prevent_initial_call=True,
)
def get_data(login_button, username, password):
    if login_button is None:
        pass
    elif login_button:
        if username and password is not None:
            login_test = login(username, password)
            if login_test:

                utils.cookieJar.set(f"ActiveUsers", login_test['httpuser']['signed_obj'], path='/')

                utils.add_user(cookie=login_test["httpuser"]["signed_obj"], user_id=login_test["httpuser"]["id"], username=login_test["httpuser"]["username"], email=login_test["httpuser"]["email"], time_logged_in=login_test["httpuser"]["time_logged_in"], avatar='https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fvignette.wikia.nocookie.net%2F2007scape%2Fimages%2F0%2F0a%2FWise_Old_Man.png%2Frevision%2Flatest%3Fcb%3D20171211175901&f=1&nofb=1&ipt=33ca4010bb53bcedb0cf0c745a4732c5dff3282fd9dbbf1dbc97efff2582ada4&ipo=images')
                # todo: Added a cache option might use might remove depending on how I want to handle the user data
                cache = diskcache.Cache(
                    f"./cache/accounts/{login_test['httpuser']['signed_obj']}"
                )
                DiskcacheManager(cache, cache_by=[lambda: launch_uid], expire=259200)
                # Idk if i should use cache for this problem?? Think session would work 👀
                # cache["username"] = login_test["HTTP user"]["username"]
                # cache["email"] = login_test["httpuser"]["email"]
                # cache["user_id"] = login_test["httpuser"]["id"]
                # cache["logged_in"] = True
                # cache["time_logged_in"] = login_test["httpuser"]["time_logged_in"]
                # cache["signed_obj"] = login_test["httpuser"]["signed_obj"]

                session.clear()
                session.user = login_test["httpuser"]

                print('Testing Session')
                print(session)
                print()
                values = session.values
                print('values')

                session_value = session.user
                print("testing session on login This is the session value")
                print(session_value())
                print(type(session_value()))
                print(session_value()["username"])


                welcome_back_alert = dmc.Alert(
                    f'Hey, {login_test["httpuser"]["username"]} you just logged in. Best way to take full advantage of this powerful Ai is to build up your account and we will help get you new upgrades, tips, tools & cheatcodes.',
                    title="Welcome Back to Maply.io",
                    color="green",
                    id="welcome-back-alert",
                    withCloseButton=True,
                    hide=False,
                )

                # TODO: Add a smart render for the logged in users avator to show the user's avitar is logged in
                # currently hardcoded Wise Old Man
                # saving it to store, and hiding the modal
                return (
                    welcome_back_alert,
                    True,
                    html.Center(
                        html.Img(
                            src=dash.get_asset_url(
                                f"static/profile_images/Wise_Old_Man.png"
                            ),
                            style={"width": "100px", "height": "250px"},
                        )
                    ),
                )
            else:
                pass
    else:
        pass

Hope that explains the situation more clearly. I’m able to retreave the django login authentication from the @callback but not able to setup much past that point. Figure what I need is a way to refer to session.user but when I attempt to open it i’m unable on anything other than the @callback

if cookie in session. user.keys:
     return html.div(''logged in)
else:
     return html.dive('Annonymous user')

thinking
Also, would like to thank @jinnyzor & @AnnMarieW helping me get this far. (Still Active Problem: 12-31-22)

1 Like

Forgive me, it didn’t connect right away on the initial reply but I just realized who you were @delsim , wasn’t expecting you to show up.


The biggest video I ever published on my coding Youtube channel was covering your project 3 years ago. For those that don’t know this dude is a legion :raised_hands:

Really would enjoy a conversation if you are interested, figure some back-and-forth banter would really go a long way in: “Turning complexity, into clarity”.

ehum questions for you:

  1. Implementing any advanced javascript framework Angular, React, View etc., they all are set up with an API and use that as the endpoint to talk with Django’s backend. From my understanding of Django-Plotly-Dash you are creating a dash app instance within the Django project and connecting it to the same endpoints as Django. With serving through the template tags the dash app runs independently to Django, or would it be processed and rendered last in the chain of processes needed to display a web page from Django?

  2. :exploding_head:



    Giving everyone else perspective on your accomplishment, Dash was released in 2017. Django-Plotly-Dash was practically released within the same year. Gibbs, you damn well lead the way in regard to Django & Dash. What is your perspective on how the relationship has evolved as both frameworks have progressed? Django moving into async, feel like dash 2.5 was a big upgrade.

    Understanding dash 2023 has come a long way since 2017. What is your assessment of how the project Django-Plotly-Dash has been able to adapt to the changes within dash?

  3. ukrainemapwar.com


    I currently have 563 stateless apps :sweat_smile: Idk if that’s something to brag about or atrocious. My goal was to build software to map events within the Ukraine war. My issue was with such a large dictionary of individual maps & data I wouldn’t have been able to set all this up within one app, I figured my best approach would be to set up each location as its own map and each filter as its own map. Finally I’ve decided to experiment with a new approach entirely with the scope of how large a project I’ve taken on, what is your thoughts on the schema you setup vs the one I’ve been setting up above? (As in the previous post) what limitations or improvements would you expect?

-Throwing a lot at ya, sorry… trust me I could ask many more questions lol but I’ll refrain. Just geeking.

@PipInstallPython the way django-plotly-dash operates is to execute the dash code inside of Django, rather than Flask or some other web framework. This has a couple of advantages - it means that the dash apps can access the Django ORM directly, for example, and do anything else that any other Django view can as well as leverage all of the access control and other request handing that Django wraps around each endpoint - as well as reducing the number of separate processes to manage (or provision resources for, particularly memory).

Regarding your comment about performance; did you notice some performance issue when using django-plotly-dash? From your text above it sounds like you observed some possible degredation.

Also, one note about the code snippet. Something like

    url_login = f'http://{username}:{password}@127.0.0.1:8000/api/login' 

should raise a red flag - embedding a password in plain text in a URL is generally unwise. It would leave it in log files for anyone to find, for example.

1 Like

@delsim, understand this is a complex problem. django-plotly-dash is an amazing project with many use cases that make it the right choice for most projects. My only argument is there are pro’s and cons to setting up dash within Django’s ORM directly.

In regards to performance, the complexity of the project for which I’ve been building needed me to think outside the box. I’m trying to build an interactive map that’s capable of hosting thousands of geo-locatable icons. When selected open up to full pages providing context on the location. Creating 563+ pages that’s only coverng ~1% of the world isn’t ideal… Figure if I was able to run one separate dash application to manage everything with the use of pages I could build a better frontend/mobile-friendly experience. I’ve at least wanted to test my hypothesis and rethink an alternative option for connecting Django & Dash to the only option currently available. After much frustration, I’ve actually been able to get something working! Check out this post for context.

Basically, the best way I’ve been able to set up the application’s relationship is solely through the FastAPI established with ninja, I’m using two validators. Curious where I could go to investigate the log files more in-depth, but with the way, I currently have it set up the username and password are only half the authentication. It was required to be sent to unlock the HttpBasicAuth within the API and it just runs a test on the user input to see if they have entered a username or an email and to log them into django. Then I’ve also have a set of tokens to represent their login state within the application, this token and their username is the only thing that gets sent back to dash through the setup of cookies in the application to save the user data and to create the relationship.

I’ve opened up the inspector and tried looking into dash but the only cookies I see are basic default cookies provided within dash, not sure how someone would be able to get the password of other people in this setup. I’ve been unable to find the cookies within the application itself and it’s a part of the reason I had so much trouble creating the relationship between the two applications. My solution was with the use of another @callback on top of the validator forum @callback that is run within dash at the start of rendering each page, is the only way I’ve been able to extract the cookie token within the dash application. I’m only able to the cookie from that point forward, on the basis of an initial @callback within the individual pages I’ve set up for example the profile page I built. It is displaying Django data through the API into dash this way:

Example of dash application reading django


These are the only cookies I can see publicly through the inspector, yet i know this isn’t true… maybe dash is hiding them internally?:

Inspecting Dash cookies

Maybe this isn’t the correct path to move forward with… I’m always open to change when appropriate. Really appreciate the insight and response. Just trying to experiment and see if what I’m looking to achieve is possible, have gotten further than I would have thought possible. Trying to compile what I’ve learned and see if their is value we can gain out from the hours I’ve put into learning these complex topics.

Hello @PipInstallPython,

What @delsim was alluding to, is in the server log, the url request that you are sending to the backend is in the log as plain text. This is one of the reasons most user authentication requests are post requests with the payload having the username and password, this is not logged in the server logs. :slight_smile:

This would not be stored inside the cookie response, but glad you are not sending it back. :slight_smile:

As far as the database, I’d highly recommend salting and hashing your passwords inside of the db and have the password test go through the same process to compare the strings. If someone were to gain access to your db, they would have access to username’s and passwords in plain text, and with people often reusing passwords and usernames this could be a security risk. :smiley:

@jinnyzor ah gotcha, thanks for jumping in and providing some context. Should be able to change it to a post request with a little tweaking. Understand there is still a lot of work in front of me but that’s with any project. Still believe I have some interesting points worth exploring.

Salting/hiding the password in the db is also a good idea, maybe from django.core.signing import Signer? idk gotta explore that a little more as i don’t know much about the proper way to setup something like that.

1 Like