11import os
2- import workos
2+ from workos import WorkOSClient
33import json
44from django .conf import settings
55from django .shortcuts import redirect , render
66from django .urls import reverse
7+ from pathlib import Path
8+ from dotenv import load_dotenv
9+
10+ # Load environment variables from .env file if it exists
11+ # BASE_DIR is the project root (where manage.py is located)
12+ # views.py is at: python-django-sso-example/sso/views.py
13+ # So we need to go up 2 levels to get to python-django-sso-example/
14+ BASE_DIR = Path (__file__ ).resolve ().parent .parent
15+ env_path = BASE_DIR / ".env"
16+ load_dotenv (env_path , override = False ) # Don't override existing env vars
17+
18+
19+ # Initialize WorkOS client
20+ # Note: In SDK v5+, we use WorkOSClient instance instead of workos.client module
21+ def get_workos_client ():
22+ """Get WorkOS client instance (initialized lazily)"""
23+ if not hasattr (get_workos_client , '_instance' ):
24+ # Reload .env file in case it wasn't loaded at import time
25+ load_dotenv (env_path , override = False )
26+
27+ api_key = os .getenv ("WORKOS_API_KEY" )
28+ client_id = os .getenv ("WORKOS_CLIENT_ID" )
29+ if not api_key or not client_id :
30+ raise ValueError (
31+ "WorkOS API key and client ID must be set via WORKOS_API_KEY and WORKOS_CLIENT_ID environment variables. "
32+ "Please check your .env file or export these variables."
33+ )
34+ get_workos_client ._instance = WorkOSClient (
35+ api_key = api_key ,
36+ client_id = client_id
37+ )
38+ return get_workos_client ._instance
39+
40+ # For compatibility with other examples, create workos_client variable
41+ # Initialize it if env vars are available, otherwise it will be created on first use
42+ try :
43+ if os .getenv ("WORKOS_API_KEY" ) and os .getenv ("WORKOS_CLIENT_ID" ):
44+ workos_client = WorkOSClient (
45+ api_key = os .getenv ("WORKOS_API_KEY" ),
46+ client_id = os .getenv ("WORKOS_CLIENT_ID" )
47+ )
48+ else :
49+ workos_client = None
50+ except ValueError :
51+ # If env vars aren't set at import time, use lazy initialization
52+ workos_client = None
753
8-
9- workos .api_key = os .getenv ("WORKOS_API_KEY" )
10- workos .client_id = os .getenv ("WORKOS_CLIENT_ID" )
11-
12- # In workos_django/settings.py, you can use DEBUG=True for local development,
13- # but you must use DEBUG=False in order to test the full authentication flow
14- # with the WorkOS API.
54+ # Set custom API base URL for local development
1555if settings .DEBUG :
1656 os .environ ["WORKOS_API_BASE_URL" ] = "http://localhost:8000/"
1757
1858# Constants
19- # Required: Fill in CUSTOMER_ORGANIZATION_ID for the desired organization from the WorkOS Dashboard
20-
21- CUSTOMER_ORGANIZATION_ID = "xxx"
59+ CUSTOMER_ORGANIZATION_ID = os .getenv ("CUSTOMER_ORGANIZATION_ID" )
2260REDIRECT_URI = os .getenv ("REDIRECT_URI" )
2361
2462
@@ -39,29 +77,77 @@ def login(request):
3977
4078
4179def auth (request ):
80+ if not REDIRECT_URI :
81+ return render (
82+ request ,
83+ "sso/login.html" ,
84+ {"error" : "configuration_error" , "error_description" : "REDIRECT_URI is not configured" },
85+ )
86+
87+ login_type = request .POST .get ("login_method" )
88+ if not login_type :
89+ return render (
90+ request ,
91+ "sso/login.html" ,
92+ {"error" : "missing_login_method" , "error_description" : "Login method is required" },
93+ )
4294
43- login_type = request .POST ["login_method" ]
4495 params = {"redirect_uri" : REDIRECT_URI , "state" : {}}
4596
4697 if login_type == "saml" :
98+ if not CUSTOMER_ORGANIZATION_ID :
99+ return render (
100+ request ,
101+ "sso/login.html" ,
102+ {"error" : "configuration_error" , "error_description" : "CUSTOMER_ORGANIZATION_ID is not configured" },
103+ )
47104 params ["organization_id" ] = CUSTOMER_ORGANIZATION_ID
48105 else :
49106 params ["provider" ] = login_type
50107
51- authorization_url = workos .client .sso .get_authorization_url (** params )
108+ client = workos_client if workos_client else get_workos_client ()
109+ authorization_url = client .sso .get_authorization_url (** params )
52110
53111 return redirect (authorization_url )
54112
55113
56114def auth_callback (request ):
57- code = request .GET ["code" ]
58- profile = workos .client .sso .get_profile_and_token (code )
59- p_profile = profile .to_dict ()
60- request .session ["p_profile" ] = p_profile
61- request .session ["first_name" ] = p_profile ["profile" ]["first_name" ]
62- request .session ["raw_profile" ] = p_profile ["profile" ]
63- request .session ["session_active" ] = True
64- return redirect ("login" )
115+ # Check for error response from WorkOS
116+ if "error" in request .GET :
117+ error = request .GET .get ("error" )
118+ error_description = request .GET .get ("error_description" , "An error occurred during authentication" )
119+ # Log the error and redirect back to login with error message
120+ return render (
121+ request ,
122+ "sso/login.html" ,
123+ {"error" : error , "error_description" : error_description },
124+ )
125+
126+ # Get the authorization code
127+ code = request .GET .get ("code" )
128+ if not code :
129+ return render (
130+ request ,
131+ "sso/login.html" ,
132+ {"error" : "missing_code" , "error_description" : "No authorization code received" },
133+ )
134+
135+ try :
136+ client = workos_client if workos_client else get_workos_client ()
137+ profile = client .sso .get_profile_and_token (code )
138+ # In SDK v5+, ProfileAndToken is a Pydantic model - use .dict() to convert to dict
139+ p_profile = profile .dict ()
140+ request .session ["p_profile" ] = p_profile
141+ request .session ["first_name" ] = p_profile ["profile" ]["first_name" ]
142+ request .session ["raw_profile" ] = p_profile ["profile" ]
143+ request .session ["session_active" ] = True
144+ return redirect ("login" )
145+ except Exception as e :
146+ return render (
147+ request ,
148+ "sso/login.html" ,
149+ {"error" : "authentication_error" , "error_description" : str (e )},
150+ )
65151
66152
67153def logout (request ):
0 commit comments