diff --git a/docs/my-website/docs/proxy/ui.md b/docs/my-website/docs/proxy/ui.md
index faad799df32..0a19c427c2f 100644
--- a/docs/my-website/docs/proxy/ui.md
+++ b/docs/my-website/docs/proxy/ui.md
@@ -1,4 +1,6 @@
import Image from '@theme/IdealImage';
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
# [BETA] Admin UI
@@ -26,22 +28,15 @@ general_settings:
allow_user_auth: true
```
-## 2. Setup Google SSO - Use this to Authenticate Team Members to the UI
-- Create an Oauth 2.0 Client
-
+## 2. Setup SSO/Auth for UI
- - Navigate to Google `Credenentials`
- - Create a new Oauth client ID
- - Set the `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` in your Proxy .env
-- Set Redirect URL on your Oauth 2.0 Client
- - Click on your Oauth 2.0 client on https://console.cloud.google.com/
- - Set a redirect url = `/google-callback`
- ```
- https://litellm-production-7002.up.railway.app/google-callback
- ```
-
-## 3. Required env variables on your Proxy
+
+
+
+- Create a new Oauth 2.0 Client on https://console.cloud.google.com/
+
+**Required .env variables on your Proxy**
```shell
PROXY_BASE_URL="" example PROXY_BASE_URL=https://litellm-production-7002.up.railway.app/
@@ -50,6 +45,36 @@ GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
```
+- Set Redirect URL on your Oauth 2.0 Client on https://console.cloud.google.com/
+ - Set a redirect url = `/sso/callback`
+ ```shell
+ https://litellm-production-7002.up.railway.app/sso/callback
+ ```
+
+
+
+
+
+- Create a new App Registration on https://portal.azure.com/
+- Create a client Secret for your App Registration
+
+**Required .env variables on your Proxy**
+```shell
+PROXY_BASE_URL="" example PROXY_BASE_URL=https://litellm-production-7002.up.railway.app/
+
+MICROSOFT_CLIENT_ID="84583a4d-"
+MICROSOFT_CLIENT_SECRET="nbk8Q~"
+MICROSOFT_TENANT="5a39737
+```
+- Set Redirect URI on your App Registration on https://portal.azure.com/
+ - Set a redirect url = `/sso/callback`
+ ```shell
+ http://localhost:4000/sso/callback
+ ```
+
+
+
+
## 4. Use UI
👉 Get Started here: https://litellm-dashboard.vercel.app/
diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py
index 4a00db6570d..f1ec2744cd6 100644
--- a/litellm/proxy/proxy_server.py
+++ b/litellm/proxy/proxy_server.py
@@ -2885,7 +2885,7 @@ async def user_auth(request: Request):
return "Email sent!"
-@app.get("/google-login/key/generate", tags=["experimental"])
+@app.get("/sso/key/generate", tags=["experimental"])
async def google_login(request: Request):
"""
Create Proxy API Keys using Google Workspace SSO. Requires setting GOOGLE_REDIRECT_URI in .env
@@ -2894,126 +2894,165 @@ async def google_login(request: Request):
Example:
"""
- GOOGLE_REDIRECT_URI = os.getenv("PROXY_BASE_URL")
- if GOOGLE_REDIRECT_URI is None:
+ microsoft_client_id = os.getenv("MICROSOFT_CLIENT_ID", None)
+ google_client_id = os.getenv("GOOGLE_CLIENT_ID", None)
+ redirect_url = os.getenv("PROXY_BASE_URL", None)
+ if redirect_url is None:
raise ProxyException(
message="PROXY_BASE_URL not set. Set it in .env file",
type="auth_error",
param="PROXY_BASE_URL",
code=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
- if GOOGLE_REDIRECT_URI.endswith("/"):
- GOOGLE_REDIRECT_URI += "google-callback"
- else:
- GOOGLE_REDIRECT_URI += "/google-callback"
- GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
- if GOOGLE_CLIENT_ID is None:
- GOOGLE_CLIENT_ID = (
- "246483686424-clje5sggkjma26ilktj6qssakqhoon0m.apps.googleusercontent.com"
+ if redirect_url.endswith("/"):
+ redirect_url += "sso/callback"
+ else:
+ redirect_url += "/sso/callback"
+ # Google SSO Auth
+ if google_client_id is not None:
+ from fastapi_sso.sso.google import GoogleSSO
+
+ google_client_secret = os.getenv("GOOGLE_CLIENT_SECRET", None)
+ if google_client_secret is None:
+ raise ProxyException(
+ message="GOOGLE_CLIENT_SECRET not set. Set it in .env file",
+ type="auth_error",
+ param="GOOGLE_CLIENT_SECRET",
+ code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ )
+
+ google_sso = GoogleSSO(
+ client_id=google_client_id,
+ client_secret=google_client_secret,
+ redirect_uri=redirect_url,
)
- verbose_proxy_logger.info(
- f"In /google-login/key/generate, \nGOOGLE_REDIRECT_URI: {GOOGLE_REDIRECT_URI}\nGOOGLE_CLIENT_ID: {GOOGLE_CLIENT_ID}"
- )
- google_auth_url = f"https://accounts.google.com/o/oauth2/auth?client_id={GOOGLE_CLIENT_ID}&redirect_uri={GOOGLE_REDIRECT_URI}&response_type=code&scope=openid%20profile%20email"
- return RedirectResponse(url=google_auth_url)
+ verbose_proxy_logger.info(
+ f"In /google-login/key/generate, \nGOOGLE_REDIRECT_URI: {redirect_url}\nGOOGLE_CLIENT_ID: {google_client_id}"
+ )
+
+ with google_sso:
+ return await google_sso.get_login_redirect()
+
+ # Microsoft SSO Auth
+ elif microsoft_client_id is not None:
+ from fastapi_sso.sso.microsoft import MicrosoftSSO
+
+ microsoft_client_secret = os.getenv("MICROSOFT_CLIENT_SECRET", None)
+ microsoft_tenant = os.getenv("MICROSOFT_TENANT", None)
+ if microsoft_client_secret is None:
+ raise ProxyException(
+ message="MICROSOFT_CLIENT_SECRET not set. Set it in .env file",
+ type="auth_error",
+ param="MICROSOFT_CLIENT_SECRET",
+ code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ )
+
+ microsoft_sso = MicrosoftSSO(
+ client_id=microsoft_client_id,
+ client_secret=microsoft_client_secret,
+ tenant=microsoft_tenant,
+ redirect_uri=redirect_url,
+ allow_insecure_http=True,
+ )
+ with microsoft_sso:
+ return await microsoft_sso.get_login_redirect()
-@app.get("/google-callback", tags=["experimental"], response_model=GenerateKeyResponse)
-async def google_callback(code: str, request: Request):
- import httpx
+@app.get("/sso/callback", tags=["experimental"])
+async def auth_callback(request: Request):
+ """Verify login"""
+ microsoft_client_id = os.getenv("MICROSOFT_CLIENT_ID", None)
+ google_client_id = os.getenv("GOOGLE_CLIENT_ID", None)
- GOOGLE_REDIRECT_URI = os.getenv("PROXY_BASE_URL")
- if GOOGLE_REDIRECT_URI is None:
+ redirect_url = os.getenv("PROXY_BASE_URL", None)
+ if redirect_url is None:
raise ProxyException(
message="PROXY_BASE_URL not set. Set it in .env file",
type="auth_error",
param="PROXY_BASE_URL",
code=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
- # Add "/google-callback"" to your callback URL
- if GOOGLE_REDIRECT_URI.endswith("/"):
- GOOGLE_REDIRECT_URI += "google-callback"
+ if redirect_url.endswith("/"):
+ redirect_url += "sso/callback"
else:
- GOOGLE_REDIRECT_URI += "/google-callback"
+ redirect_url += "/sso/callback"
- GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
- if GOOGLE_CLIENT_ID is None:
- GOOGLE_CLIENT_ID = (
- "246483686424-clje5sggkjma26ilktj6qssakqhoon0m.apps.googleusercontent.com"
+ if google_client_id is not None:
+ from fastapi_sso.sso.google import GoogleSSO
+
+ google_client_secret = os.getenv("GOOGLE_CLIENT_SECRET", None)
+ if google_client_secret is None:
+ raise ProxyException(
+ message="GOOGLE_CLIENT_SECRET not set. Set it in .env file",
+ type="auth_error",
+ param="GOOGLE_CLIENT_SECRET",
+ code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ )
+ google_sso = GoogleSSO(
+ client_id=google_client_id,
+ redirect_uri=redirect_url,
+ client_secret=google_client_secret,
)
+ result = await google_sso.verify_and_process(request)
- GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
- if GOOGLE_CLIENT_SECRET is None:
- GOOGLE_CLIENT_SECRET = "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"
+ elif microsoft_client_id is not None:
+ from fastapi_sso.sso.microsoft import MicrosoftSSO
- verbose_proxy_logger.info(
- f"/google-callback\n GOOGLE_REDIRECT_URI: {GOOGLE_REDIRECT_URI}\n GOOGLE_CLIENT_ID: {GOOGLE_CLIENT_ID}"
+ microsoft_client_secret = os.getenv("MICROSOFT_CLIENT_SECRET", None)
+ microsoft_tenant = os.getenv("MICROSOFT_TENANT", None)
+ if microsoft_client_secret is None:
+ raise ProxyException(
+ message="MICROSOFT_CLIENT_SECRET not set. Set it in .env file",
+ type="auth_error",
+ param="MICROSOFT_CLIENT_SECRET",
+ code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ )
+ if microsoft_tenant is None:
+ raise ProxyException(
+ message="MICROSOFT_TENANT not set. Set it in .env file",
+ type="auth_error",
+ param="MICROSOFT_TENANT",
+ code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ )
+
+ microsoft_sso = MicrosoftSSO(
+ client_id=microsoft_client_id,
+ client_secret=microsoft_client_secret,
+ tenant=microsoft_tenant,
+ redirect_uri=redirect_url,
+ allow_insecure_http=True,
+ )
+ result = await microsoft_sso.verify_and_process(request)
+
+ # User is Authe'd in - generate key for the UI to access Proxy
+ user_id = getattr(result, "email", None)
+ if user_id is None:
+ user_id = getattr(result, "first_name", "") + getattr(result, "last_name", "")
+
+ response = await generate_key_helper_fn(
+ **{"duration": "24hr", "models": [], "aliases": {}, "config": {}, "spend": 0, "user_id": user_id, "team_id": "litellm-dashboard"} # type: ignore
)
- # Exchange code for access token
- async with httpx.AsyncClient() as client:
- token_url = f"https://oauth2.googleapis.com/token"
- data = {
- "code": code,
- "client_id": GOOGLE_CLIENT_ID,
- "client_secret": GOOGLE_CLIENT_SECRET,
- "redirect_uri": GOOGLE_REDIRECT_URI,
- "grant_type": "authorization_code",
- }
- response = await client.post(token_url, data=data)
- # Process the response, extract user info, etc.
- if response.status_code == 200:
- access_token = response.json()["access_token"]
+ key = response["token"] # type: ignore
+ user_id = response["user_id"] # type: ignore
+ litellm_dashboard_ui = "https://litellm-dashboard.vercel.app/"
- # Fetch user info using the access token
- async with httpx.AsyncClient() as client:
- user_info_url = "https://www.googleapis.com/oauth2/v1/userinfo"
- headers = {"Authorization": f"Bearer {access_token}"}
- user_info_response = await client.get(user_info_url, headers=headers)
+ # if user set LITELLM_UI_LINK in .env, use that
+ litellm_ui_link_in_env = os.getenv("LITELLM_UI_LINK", None)
+ if litellm_ui_link_in_env is not None:
+ litellm_dashboard_ui = litellm_ui_link_in_env
- # Process user info response
- if user_info_response.status_code == 200:
- user_info = user_info_response.json()
- user_email = user_info.get("email")
- user_name = user_info.get("name")
-
- # we can use user_email on litellm proxy now
-
- # TODO: Handle user info as needed, for example, store it in a database, authenticate the user, etc.
- response = await generate_key_helper_fn(
- **{"duration": "24hr", "models": [], "aliases": {}, "config": {}, "spend": 0, "user_id": user_email, "team_id": "litellm-dashboard"} # type: ignore
- )
-
- key = response["token"] # type: ignore
- user_id = response["user_id"] # type: ignore
- litellm_dashboard_ui = "https://litellm-dashboard.vercel.app/"
-
- # if user set LITELLM_UI_LINK in .env, use that
- litellm_ui_link_in_env = os.getenv("LITELLM_UI_LINK", None)
- if litellm_ui_link_in_env is not None:
- litellm_dashboard_ui = litellm_ui_link_in_env
-
- litellm_dashboard_ui += (
- "?userID="
- + user_id
- + "&accessToken="
- + key
- + "&proxyBaseUrl="
- + os.getenv("PROXY_BASE_URL")
- )
- return RedirectResponse(url=litellm_dashboard_ui)
-
- else:
- # Handle user info retrieval error
- raise HTTPException(
- status_code=user_info_response.status_code,
- detail=user_info_response.text,
- )
- else:
- # Handle the error from the token exchange
- raise HTTPException(status_code=response.status_code, detail=response.text)
+ litellm_dashboard_ui += (
+ "?userID="
+ + user_id
+ + "&accessToken="
+ + key
+ + "&proxyBaseUrl="
+ + os.getenv("PROXY_BASE_URL")
+ )
+ return RedirectResponse(url=litellm_dashboard_ui)
@router.get(
diff --git a/ui/litellm-dashboard/src/components/user_dashboard.tsx b/ui/litellm-dashboard/src/components/user_dashboard.tsx
index fadad17b501..f66d39fba58 100644
--- a/ui/litellm-dashboard/src/components/user_dashboard.tsx
+++ b/ui/litellm-dashboard/src/components/user_dashboard.tsx
@@ -43,11 +43,10 @@ const UserDashboard = () => {
);
}
else if (userID == null || accessToken == null) {
- // redirect to page: ProxyBaseUrl/google-login/key/generate
const baseUrl = proxyBaseUrl.endsWith('/') ? proxyBaseUrl : proxyBaseUrl + '/';
// Now you can construct the full URL
- const url = `${baseUrl}google-login/key/generate`;
+ const url = `${baseUrl}sso/key/generate`;
window.location.href = url;