Py.Cafe

maartenbreddels/

solara-auth-example

Authorization and authentication example - Solara

DocsPricing
  • app.py
  • requirements.txt
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""# Authorization

Authorization is a common requirement for web applications. This example shows how to implement a simple login form and
how to use `use_route` to implement authorization.

The `Layout` component checks if the current route requires authorization and if the user is logged in. If not, it
redirects to the login form.
"""

import asyncio
import dataclasses
from typing import Optional, cast

import solara
import solara.lab

github_url = solara.util.github_url(__file__)

route_order = ["/", "users", "admin"]


def check_auth(route, children):
    # This can be replaced by a custom function that checks if the user is
    # logged in and has the required permissions.

    # routes that are public or only for admin
    # the rest only requires login
    public_paths = ["/"]
    admin_paths = ["admin"]

    if route.path in public_paths:
        children_auth = children
    else:
        if user.value is None:
            children_auth = [LoginForm()]
        else:
            if route.path in admin_paths and not user.value.admin:
                children_auth = [solara.Error("You are not an admin")]
            else:
                children_auth = children
    return children_auth


@dataclasses.dataclass
class User:
    username: str
    admin: bool = False


user = solara.reactive(cast(Optional[User], None))
login_failed = solara.reactive(False)


@solara.lab.task
async def login(username: str, password: str):
    await asyncio.sleep(0.5)
    # this function can be replace by a custom username/password check
    if username == "test" and password == "test":
        user.value = User(username, admin=False)
        login_failed.value = False
    elif username == "admin" and password == "admin":
        user.value = User(username, admin=True)
        login_failed.value = False
    else:
        login_failed.value = True


@solara.component
def HomePage():
    solara.Markdown("This page is visible for everyone")

    solara.Markdown(__doc__)
    solara.Button(label="View source", icon_name="mdi-github-circle", attributes={"href": github_url, "target": "_blank"}, text=True, outlined=True)


@solara.component
def UserPage():
    assert user.value is not None
    solara.Markdown(f"Hi {user.value.username}!")
    solara.Button(label="View source", icon_name="mdi-github-circle", attributes={"href": github_url, "target": "_blank"}, text=True, outlined=True)


@solara.component
def AdminPage():
    assert user.value is not None
    solara.Markdown(f"Hi {user.value.username}, you are an admin")
    solara.Button(label="View source", icon_name="mdi-github-circle", attributes={"href": github_url, "target": "_blank"}, text=True, outlined=True)



@solara.component
def LoginForm():
    username = solara.use_reactive("")
    password = solara.use_reactive("")
    with solara.Card("Login"):
        solara.Markdown(
            """
        This is an example login form.

          * use admin/admin to login as admin.
          * use test/test to login as a normal user.
        """
        )
        solara.InputText(label="Username", value=username)
        solara.InputText(label="Password", password=True, value=password)
        solara.Button(label="Login", on_click=lambda: login(username.value, password.value), disabled=login.pending)
        solara.ProgressLinear(login.pending)
        if login_failed.value:
            solara.Error("Wrong username or password")


@solara.component
def Layout(children=[]):
    route, routes = solara.use_route(peek=True)
    if route is None:
        return solara.Error("Route not found")

    children = check_auth(route, children)

    with solara.AppLayout(children=children, navigation=True):
        with solara.AppBar():
            with solara.lab.Tabs(align="center"):
                for route in routes:
                    name = route.path if route.path != "/" else "Home"
                    is_admin = user.value and user.value.admin
                    # we could skip the admin tab if the user is not an admin
                    # if route.path == "admin" and not is_admin:
                    #     continue
                    # in this case we disable the tab
                    disabled = route.path == "admin" and not is_admin
                    solara.lab.Tab(name, path_or_route=route, disabled=disabled)
            if user.value:
                solara.Text(f"Logged in as {user.value.username} as {'admin' if user.value.admin else 'user'}")
                with solara.Tooltip("Logout"):
                    solara.Button(icon_name="mdi-logout", icon=True, on_click=lambda: user.set(None))
            else:
                with solara.AppBar():
                    solara.Text("Not logged in")

routes = [
    solara.Route("/", component=HomePage, layout=Layout),
    solara.Route("users", component=UserPage),
    solara.Route("admin", component=AdminPage),

]