-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Open
Labels
customer-reportedIssues that are reported by GitHub users external to the Azure organization.Issues that are reported by GitHub users external to the Azure organization.needs-triageWorkflow: This is a new issue that needs to be triaged to the appropriate team.Workflow: This is a new issue that needs to be triaged to the appropriate team.questionThe issue doesn't require a change to the product in order to be resolved. Most issues start as thatThe issue doesn't require a change to the product in order to be resolved. Most issues start as that
Description
Hello,
I’m building a web app in Python using NiceGUI that should access sharepoint files via MS Graph. I've registered the application in Entra as a web app with delegated access.
I can successfully create an AuthorizationCodeCredential with an authorization code and make an initial Microsoft Graph calls. However, I can't figure out on how to do subsequent requests creating a new graph client (new page / new client instance).
My questions:
- Do I have to save the authorization code and recreate AuthorizationCodeCredential everytime i want to create a new GraphServiceClient?
- Should I somehow store the first creation of GraphServiceClient?
- Or can I use the credentials from the first call to get a new token to create a new GraphServiceClient?
import os
import uuid
import urllib.parse
from dotenv import load_dotenv
from nicegui import ui, app, Client
from fastapi.responses import RedirectResponse
from azure.identity import AuthorizationCodeCredential
from msgraph import GraphServiceClient
# --------------------
# Configuration
# --------------------
load_dotenv()
CLIENT_ID = os.environ.get("AZURE_ENTRA_APPLICATION_CLIENT_ID")
CLIENT_SECRET = os.environ.get("AZURE_ENTRA_APPLICATION_CLIENT_SECRET")
TENANT_ID = os.environ.get("AZURE_TENANT_ID")
AUTHORITY = f"https://login.microsoftonline.com/{TENANT_ID}"
SCOPES = ["User.Read", "Sites.Read.All"]
BASE_URL = "http://localhost:8080"
REDIRECT_PATH = "/auth/callback"
REDIRECT_URI = f"{BASE_URL}{REDIRECT_PATH}"
LOGOUT_URL = (
f"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/logout"
f"?post_logout_redirect_uri={BASE_URL}"
)
def build_login_url(state: str) -> str:
params = {
"client_id": CLIENT_ID,
"response_type": "code",
"redirect_uri": REDIRECT_URI,
"response_mode": "query",
"scope": " ".join(SCOPES),
"state": state,
}
return (
f"{AUTHORITY}/oauth2/v2.0/authorize?"
+ urllib.parse.urlencode(params)
)
@ui.page("/")
def index(client: Client):
user = app.storage.user.get("user")
if user:
ui.label(f"Welcome {user['name']}")
ui.button("Go to app", on_click=lambda: ui.navigate.to("/app"))
ui.button("Logout", on_click=lambda: ui.navigate.to("/logout"))
else:
ui.label("Please log in")
# ui.button("Login with Microsoft", on_click=lambda: ui.navigate.to("/login"))
ui.button("Login with Microsoft", on_click=lambda: ui.run_javascript(
f'window.location.href="/login"'
))
@ui.page("/login")
def login():
# CSRF protection
state = str(uuid.uuid4())
app.storage.browser["oauth_state"] = state
return RedirectResponse(build_login_url(state))
@ui.page(REDIRECT_PATH)
async def auth_callback(client: Client):
params = client.request.query_params
code = params.get("code")
state = params.get("state")
# Validate state
if state != app.storage.browser.get("oauth_state"):
ui.notify("Invalid login state", type="negative")
return ui.navigate.to("/")
if not code:
ui.notify("No authorization code received", type="negative")
return ui.navigate.to("/")
# Create credential using auth code
credential = AuthorizationCodeCredential(
tenant_id=TENANT_ID,
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
authorization_code=code,
redirect_uri=REDIRECT_URI,
)
# Use Graph client
graph = GraphServiceClient(credential, SCOPES)
user = await graph.me.get()
app.storage.user["user"] = {
"name": user.display_name,
"email": user.mail or user.user_principal_name,
"id": user.id,
}
ui.navigate.to("/")
@ui.page("/app")
def actual_app():
user = app.storage.user.get("user")
if not user:
return ui.navigate.to("/")
ui.markdown(
f"""
## Logged in 🎉
**Name:** {user['name']}
**Email:** {user['email']}
"""
)
ui.button("Show Sites", on_click=lambda: ui.navigate.to("/sites"))
ui.button("Logout", on_click=lambda: ui.navigate.to("/logout"))
@ui.page("/sites")
async def list_sites():
# HERE I'm stuck. How to recreate the graph client?
graph = create_graph_client()
site = await graph.sites.by_site_id()
....
@ui.page("/logout")
def logout():
app.storage.user.clear()
return RedirectResponse(LOGOUT_URL)
ui.run(
host="0.0.0.0",
port=8080,
storage_secret=str(uuid.uuid4()),
)
Related context:
- Something similar, but not using the AuthorizationCodeCredential: [How to authenticate using a user's raw access token? microsoftgraph/msgraph-sdk-python#501]
- Similar discussion in NiceGUI community using msal but not for ms graph: [Show and tell: Deploying NiceGUI app in Azure WebApp with Authentication zauberzeug/nicegui#3624] with the example code
Thank you for you help.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
customer-reportedIssues that are reported by GitHub users external to the Azure organization.Issues that are reported by GitHub users external to the Azure organization.needs-triageWorkflow: This is a new issue that needs to be triaged to the appropriate team.Workflow: This is a new issue that needs to be triaged to the appropriate team.questionThe issue doesn't require a change to the product in order to be resolved. Most issues start as thatThe issue doesn't require a change to the product in order to be resolved. Most issues start as that