Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,14 @@ def from_env(cls) -> Self:
"callback_url": os.environ.get("AUTOGENSTUDIO_GITHUB_CALLBACK_URL", ""),
"scopes": os.environ.get("AUTOGENSTUDIO_GITHUB_SCOPES", "user:email").split(","),
}
# Add other provider config parsing here
elif auth_type == "msal":
config_dict["msal"] = {
"tenant_id": os.environ.get("AUTOGENSTUDIO_MSAL_TENANT_ID", ""),
"client_id": os.environ.get("AUTOGENSTUDIO_MSAL_CLIENT_ID", ""),
"client_secret": os.environ.get("AUTOGENSTUDIO_MSAL_CLIENT_SECRET", ""),
"callback_url": os.environ.get("AUTOGENSTUDIO_MSAL_CALLBACK_URL", ""),
"scopes": os.environ.get("AUTOGENSTUDIO_MSAL_SCOPES", "User.Read").split(","),
}

config = AuthConfig(**config_dict)
return cls(config)
99 changes: 88 additions & 11 deletions python/packages/autogen-studio/autogenstudio/web/auth/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from urllib.parse import urlencode

import httpx
import msal
from loguru import logger

from .exceptions import ConfigurationException, ProviderAuthException
Expand Down Expand Up @@ -160,23 +161,99 @@ def __init__(self, config: AuthConfig):
raise ConfigurationException("MSAL auth configuration is missing")

self.config = config.msal
# MSAL provider implementation would go here
# This is a placeholder - full implementation would use msal library
self.tenant_id = self.config.tenant_id
self.client_id = self.config.client_id
self.client_secret = self.config.client_secret
self.callback_url = self.config.callback_url
self.scopes = self.config.scopes

# Initialize MSAL Confidential Client Application
authority = f"https://login.microsoftonline.com/{self.tenant_id}"
self.msal_app = msal.ConfidentialClientApplication(
client_id=self.client_id,
client_credential=self.client_secret,
authority=authority,
)

async def get_login_url(self) -> str:
"""Return the MSAL OAuth login URL."""
# Placeholder - would use MSAL library to generate auth URL
return "https://login.microsoftonline.com/placeholder"
"""Return the Microsoft OAuth login URL."""
state = secrets.token_urlsafe(32) # Generate a secure random state
auth_url = self.msal_app.get_authorization_request_url(
scopes=self.scopes,
state=state,
redirect_uri=self.callback_url,
)
return auth_url

async def process_callback(self, code: str, state: str | None = None) -> User:
"""Process the MSAL callback."""
# Placeholder - would use MSAL library to process code and get token/user info
return User(id="msal_user_id", name="MSAL User", provider="msal")
"""Exchange code for access token and get user info."""
if not code:
raise ProviderAuthException("msal", "Authorization code is missing")

try:
# Exchange code for access token
result = self.msal_app.acquire_token_by_authorization_code(
code=code,
scopes=self.scopes,
redirect_uri=self.callback_url,
)

if "error" in result:
logger.error(f"MSAL token exchange failed: {result.get('error_description', result.get('error'))}")
raise ProviderAuthException("msal", f"Failed to exchange code for access token: {result.get('error')}")

access_token = result.get("access_token")
if not access_token:
logger.error(f"No access token in MSAL response: {result}")
raise ProviderAuthException("msal", "No access token received")

# Get user info with the access token
async with httpx.AsyncClient() as client:
user_response = await client.get(
"https://graph.microsoft.com/v1.0/me",
headers={"Authorization": f"Bearer {access_token}", "Accept": "application/json"},
)

if user_response.status_code != 200:
logger.error(f"Microsoft Graph user info fetch failed: {user_response.text}")
raise ProviderAuthException("msal", "Failed to fetch user information")

user_data = user_response.json()

# Create User object
return User(
id=str(user_data.get("id")),
name=user_data.get("displayName") or user_data.get("userPrincipalName"),
email=user_data.get("mail") or user_data.get("userPrincipalName"),
avatar_url=None, # Microsoft Graph doesn't provide avatar URL in basic profile
provider="msal",
metadata={
"user_principal_name": user_data.get("userPrincipalName"),
"tenant_id": self.tenant_id,
"object_id": user_data.get("id"),
"access_token": access_token,
"id_token": result.get("id_token"),
Comment on lines +234 to +235
Copy link

Copilot AI Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing the access token in user metadata could pose a security risk if this data is logged, cached, or exposed through APIs. Consider whether the access token needs to be stored or if it should be handled more securely.

Suggested change
"access_token": access_token,
"id_token": result.get("id_token"),

Copilot uses AI. Check for mistakes.
Copy link
Author

@oumizx oumizx Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It follows the same pattern as GithubAuthProvider. GithubAuthProvider also stores token in metadata.


To fix it, I think we need a separate PR.

},
)

except Exception as e:
if isinstance(e, ProviderAuthException):
raise
logger.error(f"MSAL authentication error: {str(e)}")
raise ProviderAuthException("msal", f"Authentication failed: {str(e)}")

async def validate_token(self, token: str) -> bool:
"""Validate an MSAL token."""
# Placeholder - would validate token with MSAL library
return False
"""Validate a Microsoft access token."""
try:
async with httpx.AsyncClient() as client:
response = await client.get(
"https://graph.microsoft.com/v1.0/me",
headers={"Authorization": f"Bearer {token}", "Accept": "application/json"},
)
return response.status_code == 200
except Exception as e:
logger.error(f"Token validation error: {str(e)}")
return False


class FirebaseAuthProvider(AuthProvider):
Expand Down
9 changes: 6 additions & 3 deletions python/packages/autogen-studio/frontend/src/auth/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
isValidMessageOrigin,
isValidUserObject,
} from "../components/utils/security-utils";
import { getAuthProviderInfo } from "./utils";

interface AuthContextType {
user: User | null;
Expand Down Expand Up @@ -111,13 +112,15 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
// Update user state
setUser(data.user);

// Show success message
message.success("Successfully logged in");
// Show success message with provider name
const providerInfo = getAuthProviderInfo(data.user.provider || authType);
message.success(`Successfully signed in with ${providerInfo.displayName}`);

// Redirect to home
navigate(sanitizeRedirectUrl("/"));
} else if (data.type === "auth-error") {
message.error(`Authentication failed: ${data.error}`);
const providerInfo = getAuthProviderInfo(authType);
message.error(`${providerInfo.displayName} authentication failed: ${data.error}`);
}
};

Expand Down
53 changes: 53 additions & 0 deletions python/packages/autogen-studio/frontend/src/auth/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from "react";
import { GithubOutlined, WindowsOutlined } from "@ant-design/icons";

export interface AuthProviderInfo {
name: string;
displayName: string;
icon: React.ReactNode;
color: string;
connectingText: string;
buttonText: string;
}

export const getAuthProviderInfo = (authType: string): AuthProviderInfo => {
switch (authType) {
case "github":
return {
name: "github",
displayName: "GitHub",
icon: <GithubOutlined />,
color: "#24292f",
connectingText: "Connecting to GitHub...",
buttonText: "Sign in with GitHub",
};

case "msal":
return {
name: "msal",
displayName: "Microsoft",
icon: <WindowsOutlined />,
color: "#0078d4",
connectingText: "Connecting to Microsoft...",
buttonText: "Sign in with Microsoft",
};

default:
return {
name: "unknown",
displayName: "External Provider",
icon: <GithubOutlined />, // fallback icon
Copy link

Copilot AI Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using GitHub icon as fallback for unknown auth providers is misleading. Consider using a generic authentication icon or a question mark icon instead.

Copilot uses AI. Check for mistakes.
Copy link
Author

@oumizx oumizx Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed from GithubOutlined to LoginOutlined which provides a generic authentication icon that's more appropriate for unknown providers.

color: "#1890ff",
connectingText: "Connecting...",
buttonText: "Sign in",
};
}
};

export const getPopupWindowName = (authType: string): string => {
return `${authType}-auth`;
};

export const isAuthEnabled = (authType: string): boolean => {
return authType !== "none";
};
14 changes: 11 additions & 3 deletions python/packages/autogen-studio/frontend/src/pages/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { GithubOutlined } from "@ant-design/icons";
import Layout from "../components/layout";
import { graphql } from "gatsby";
import Icon from "../components/icons";
import { getAuthProviderInfo, getPopupWindowName } from "../auth/utils";

const { Title, Text } = Typography;

Expand All @@ -15,6 +16,9 @@ const TOKEN_KEY = "auth_token";
const LoginPage = ({ data }: any) => {
const { isAuthenticated, isLoading, login, authType } = useAuth();
const [isLoggingIn, setIsLoggingIn] = useState(false);

// Get provider-specific UI information
const providerInfo = getAuthProviderInfo(authType);

useEffect(() => {
// If user is already authenticated, redirect to home
Expand Down Expand Up @@ -49,7 +53,7 @@ const LoginPage = ({ data }: any) => {

const popup = window.open(
loginUrl,
"github-auth",
getPopupWindowName(authType),
`width=${width},height=${height},top=${top},left=${left}`
);

Expand Down Expand Up @@ -113,12 +117,16 @@ const LoginPage = ({ data }: any) => {
<Button
type="primary"
size="large"
icon={<GithubOutlined />}
icon={providerInfo.icon}
onClick={handleLogin}
loading={isLoggingIn}
block
style={{
backgroundColor: isLoggingIn ? undefined : providerInfo.color,
borderColor: isLoggingIn ? undefined : providerInfo.color
}}
>
{isLoggingIn ? "Connecting to GitHub..." : "Sign in with GitHub"}
{isLoggingIn ? providerInfo.connectingText : providerInfo.buttonText}
</Button>
</Space>
</div>
Expand Down
3 changes: 2 additions & 1 deletion python/packages/autogen-studio/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ dependencies = [
"autogen-agentchat>=0.4.9.2,<0.7",
"autogen-ext[magentic-one, openai, azure, mcp]>=0.4.2,<0.7",
"anthropic",
"mcp>=1.11.0"
"mcp>=1.11.0",
"msal>=1.24.0"
]
optional-dependencies = {web = ["fastapi", "uvicorn"], database = ["psycopg"]}

Expand Down
Loading