Skip to content

Commit

Permalink
Add /logout functionality for Gradio auth (#7547)
Browse files Browse the repository at this point in the history
* logout

* changes

* add changeset

* documentation

* add test

* changes

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
  • Loading branch information
abidlabs and gradio-pr-bot committed Feb 27, 2024
1 parent f84720c commit 98aa808
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/silent-corners-mix.md
@@ -0,0 +1,5 @@
---
"gradio": minor
---

feat:Add `/logout` functionality for Gradio auth
18 changes: 16 additions & 2 deletions gradio/routes.py
Expand Up @@ -28,7 +28,7 @@
import httpx
import markupsafe
import orjson
from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException, status
from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException, Response, status
from fastapi.responses import (
FileResponse,
HTMLResponse,
Expand Down Expand Up @@ -288,9 +288,23 @@ def login(form_data: OAuth2PasswordRequestForm = Depends()):
###############

# Define OAuth routes if the app expects it (i.e. a LoginButton is defined).
# It allows users to "Sign in with HuggingFace".
# It allows users to "Sign in with HuggingFace". Otherwise, add the default
# logout route.
if app.blocks is not None and app.blocks.expects_oauth:
attach_oauth(app)
else:

@app.get("/logout")
def logout(response: Response, user: str = Depends(get_current_user)):
response.delete_cookie(key=f"access-token-{app.cookie_id}", path="/")
response.delete_cookie(
key=f"access-token-unsecure-{app.cookie_id}", path="/"
)
# A user may have multiple tokens, so we need to delete all of them.
for token in list(app.tokens.keys()):
if app.tokens[token] == user:
del app.tokens[token]
return RedirectResponse(url="/", status_code=status.HTTP_302_FOUND)

###############
# Main Routes
Expand Down
37 changes: 35 additions & 2 deletions guides/01_getting-started/03_sharing-your-app.md
Expand Up @@ -189,8 +189,41 @@ def same_auth(username, password):
demo.launch(auth=same_auth)
```

For authentication to work properly, third party cookies must be enabled in your browser.
This is not the case by default for Safari, Chrome Incognito Mode.
If you have multiple users, you may wish to customize the content that is shown depending on the user that is logged in. You can retrieve the logged in user by [accessing the network request directly](#accessing-the-network-request-directly) and then reading the `.username` attribute of the request. Here's an example:


```python
import gradio as gr

def update_message(request: gr.Request):
return f"Welcome, {request.username}"

with gr.Blocks() as demo:
m = gr.Markdown()
demo.load(update_message, None, m)

demo.launch(auth=[("Abubakar", "Abubakar"), ("Ali", "Ali")])
```

Note: For authentication to work properly, third party cookies must be enabled in your browser. This is not the case by default for Safari or for Chrome Incognito Mode.

If users visit the `/logout` page of your Gradio app, they will automatically be logged out and session cookies deleted. This allows you to add logout functionality to your Gradio app as well. Let's update the previous example to include a log out button:

```python
import gradio as gr

def update_message(request: gr.Request):
return f"Welcome, {request.username}"

with gr.Blocks() as demo:
m = gr.Markdown()
logout_button = gr.Button("Logout", link="/logout")
demo.load(update_message, None, m)

demo.launch(auth=[("Pete", "Pete"), ("Dawood", "Dawood")])
```

Note: Gradio's built-in authentication provides a straightforward and basic layer of access control but does not offer robust security features for applications that require stringent access controls (e.g. multi-factor authentication, rate limiting, or automatic lockout policies).

### OAuth (Login via Hugging Face)

Expand Down
32 changes: 32 additions & 0 deletions test/test_routes.py
Expand Up @@ -501,18 +501,50 @@ def test_post_login(self):
data={"username": "test", "password": "correct_password"},
)
assert response.status_code == 200

response = client.post(
"/login",
data={"username": "test", "password": "incorrect_password"},
)
assert response.status_code == 400

client.post(
"/login",
data={"username": "test", "password": "correct_password"},
)
response = client.post(
"/login",
data={"username": " test ", "password": "correct_password"},
)
assert response.status_code == 200

def test_logout(self):
io = Interface(lambda x: x, "text", "text")
app, _, _ = io.launch(
auth=("test", "correct_password"),
prevent_thread_lock=True,
)
client = TestClient(app)

client.post(
"/login",
data={"username": "test", "password": "correct_password"},
)

response = client.post(
"/run/predict",
json={"data": ["test"]},
)
assert response.status_code == 200

response = client.get("/logout")

response = client.post(
"/run/predict",
json={"data": ["test"]},
)
assert response.status_code == 401


class TestQueueRoutes:
@pytest.mark.asyncio
Expand Down

0 comments on commit 98aa808

Please sign in to comment.