Hammad712 commited on
Commit
8393a26
·
0 Parent(s):

Added SSO Backend Functionality

Browse files
.gitignore ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+ .pnpm-debug.log*
32
+
33
+ # env files (can opt-in for committing if needed)
34
+ .env*
35
+
36
+ # vercel
37
+ .vercel
38
+
39
+ # typescript
40
+ *.tsbuildinfo
41
+ next-env.d.ts
app/__init__.py ADDED
File without changes
app/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (175 Bytes). View file
 
app/__pycache__/main.cpython-312.pyc ADDED
Binary file (1.55 kB). View file
 
app/api/__init__.py ADDED
File without changes
app/api/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (179 Bytes). View file
 
app/api/v1/__init__.py ADDED
File without changes
app/api/v1/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (182 Bytes). View file
 
app/api/v1/__pycache__/api.cpython-312.pyc ADDED
Binary file (458 Bytes). View file
 
app/api/v1/api.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter
2
+ from app.api.v1.endpoints import auth
3
+
4
+ api_router = APIRouter()
5
+
6
+ api_router.include_router(auth.router, prefix="/auth", tags=["Authentication"])
app/api/v1/endpoints/__init__.py ADDED
File without changes
app/api/v1/endpoints/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (192 Bytes). View file
 
app/api/v1/endpoints/__pycache__/auth.cpython-312.pyc ADDED
Binary file (2.93 kB). View file
 
app/api/v1/endpoints/auth.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Request, HTTPException
2
+ from authlib.integrations.starlette_client import OAuth
3
+ from app.core.config import settings
4
+
5
+ router = APIRouter()
6
+
7
+ # Initialize OAuth
8
+ oauth = OAuth()
9
+
10
+ # Register Microsoft Provider
11
+ # We use the tenant-specific endpoint for better security and internal organization
12
+ CONF_URL = f"https://login.microsoftonline.com/{settings.MS_TENANT_ID}/v2.0/.well-known/openid-configuration"
13
+
14
+ oauth.register(
15
+ name='microsoft',
16
+ client_id=settings.MS_CLIENT_ID,
17
+ client_secret=settings.MS_CLIENT_SECRET,
18
+ server_metadata_url=CONF_URL,
19
+ client_kwargs={
20
+ 'scope': 'openid email profile User.Read'
21
+ }
22
+ )
23
+
24
+ @router.get("/login")
25
+ async def login(request: Request):
26
+ """
27
+ Redirects the user to the Microsoft Login page.
28
+ """
29
+ redirect_uri = settings.MS_REDIRECT_URI
30
+ return await oauth.microsoft.authorize_redirect(request, redirect_uri)
31
+
32
+
33
+ @router.get("/callback")
34
+ async def auth_callback(request: Request):
35
+ """
36
+ Handles the callback from Microsoft after successful login.
37
+ Exchanges the authorization code for an access token and user info.
38
+ """
39
+ try:
40
+ token = await oauth.microsoft.authorize_access_token(request)
41
+ user = token.get('userinfo')
42
+
43
+ if not user:
44
+ # Sometimes userinfo is not directly in the token depending on claims,
45
+ # allow fetch via userinfo endpoint if configured, or parse id_token
46
+ user = await oauth.microsoft.userinfo(token=token)
47
+
48
+ # Store user info in session (cookie)
49
+ # In a real app, you might issue your own JWT here instead
50
+ request.session['user'] = dict(user)
51
+
52
+ return {"message": "Login successful", "user": user}
53
+ except Exception as e:
54
+ # Log the error in production
55
+ raise HTTPException(status_code=400, detail=f"SSO Login Failed: {str(e)}")
56
+
57
+
58
+ @router.get("/logout")
59
+ async def logout(request: Request):
60
+ """
61
+ Clears the local session.
62
+ """
63
+ request.session.pop('user', None)
64
+ return {"message": "Logged out successfully"}
app/core/__init__.py ADDED
File without changes
app/core/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (180 Bytes). View file
 
app/core/__pycache__/config.cpython-312.pyc ADDED
Binary file (888 Bytes). View file
 
app/core/config.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic_settings import BaseSettings
2
+
3
+ class Settings(BaseSettings):
4
+ PROJECT_NAME: str = "FastAPI SSO"
5
+ SECRET_KEY: str
6
+
7
+ # Microsoft SSO Config
8
+ MS_CLIENT_ID: str
9
+ MS_CLIENT_SECRET: str
10
+ MS_TENANT_ID: str
11
+ MS_REDIRECT_URI: str
12
+
13
+ class Config:
14
+ env_file = ".env"
15
+ case_sensitive = True
16
+
17
+ settings = Settings()
app/main.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from starlette.middleware.sessions import SessionMiddleware
3
+ from starlette.requests import Request
4
+ from starlette.responses import HTMLResponse
5
+ from starlette.templating import Jinja2Templates
6
+
7
+ from app.core.config import settings
8
+ from app.api.v1.api import api_router
9
+
10
+ app = FastAPI(title=settings.PROJECT_NAME)
11
+
12
+ # 1. Add Session Middleware
13
+ # This is required for Authlib to store the temporary state during the OAuth dance
14
+ app.add_middleware(
15
+ SessionMiddleware,
16
+ secret_key=settings.SECRET_KEY,
17
+ https_only=False # Set to True in production with SSL
18
+ )
19
+
20
+ # 2. Include API Routers
21
+ app.include_router(api_router)
22
+
23
+ # 3. Simple Frontend for demonstration
24
+ templates = Jinja2Templates(directory="app/templates")
25
+
26
+ @app.get("/", response_class=HTMLResponse)
27
+ async def root(request: Request):
28
+ user = request.session.get("user")
29
+ return templates.TemplateResponse("index.html", {"request": request, "user": user})
30
+
31
+ if __name__ == "__main__":
32
+ import uvicorn
33
+ uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)
app/templates/index.html ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>FastAPI Microsoft SSO</title>
7
+ <style>
8
+ body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f4f4f4; }
9
+ .container { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); text-align: center; }
10
+ .btn { display: inline-block; padding: 10px 20px; background-color: #0078d4; color: white; text-decoration: none; border-radius: 4px; font-weight: bold; }
11
+ .btn-logout { background-color: #d83b01; }
12
+ pre { background: #eee; padding: 10px; text-align: left; overflow-x: auto; }
13
+ </style>
14
+ </head>
15
+ <body>
16
+ <div class="container">
17
+ <h1>Microsoft SSO Integration</h1>
18
+
19
+ {% if user %}
20
+ <p>Welcome, <strong>{{ user.name or user.email }}</strong>!</p>
21
+ <div style="text-align: left; margin-bottom: 20px;">
22
+ <small>User Details:</small>
23
+ <pre>{{ user | tojson(indent=2) }}</pre>
24
+ </div>
25
+ <a href="/auth/logout" class="btn btn-logout">Logout</a>
26
+ {% else %}
27
+ <p>You are not logged in.</p>
28
+ <a href="/auth/login" class="btn">Login with Microsoft</a>
29
+ {% endif %}
30
+ </div>
31
+ </body>
32
+ </html>
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ authlib
4
+ httpx
5
+ pydantic-settings
6
+ jinja2
7
+ python-multipart
8
+ itsdangerous