diff --git a/python-django-sso-example/README.md b/python-django-sso-example/README.md index 99a772f..71eb5ce 100644 --- a/python-django-sso-example/README.md +++ b/python-django-sso-example/README.md @@ -1,13 +1,15 @@ # python-django-sso-example + An example Django application demonstrating how to use the [WorkOS Python SDK](https://github.com/workos/workos-python) to authenticate users via SSO. ## Prerequisites -- Python 3.6+ +- Python 3.6+ ## Django Project Setup 1. Clone the main git repo for these Python example apps using your preferred secure method (HTTPS or SSH). + ```bash # HTTPS $ git clone https://github.com/workos/python-django-example-applications.git @@ -21,12 +23,13 @@ An example Django application demonstrating how to use the [WorkOS Python SDK](h ``` 2. Navigate to the Admin Portal example app within the cloned repo. + ```bash $ cd python-django-example-applications/python-django-sso-example - ```` - + ``` 3. Create and source a Python virtual environment. You should then see `(env)` at the beginning of your command-line prompt. + ```bash $ python3 -m venv env $ source env/bin/activate @@ -34,22 +37,26 @@ An example Django application demonstrating how to use the [WorkOS Python SDK](h ``` 4. Install the cloned app's dependencies. If the `pip` command doesn't work, try `pip3` instead. + ```bash (env) $ pip install -r requirements.txt ``` 5. Obtain and make note of the following values. In the next step, these will be set as environment variables. + - Your [WorkOS API key](https://dashboard.workos.com/api-keys) - Your [SSO-specific, WorkOS Client ID](https://dashboard.workos.com/sso/configuration) - The redirect URI. For this example, we'll use http://localhost:8000/auth/callback 6. Ensure you're in the root directory for the example app, `python-django-sso-example/`. Create a `.env` file to securely store the environment variables. Open this file with the Nano text editor. (This file is listed in this repo's `.gitignore` file, so your sensitive information will not be checked into version control.) + ```bash (env) $ touch .env (env) $ nano .env ``` 7. Once the Nano text editor opens, you can directly edit the `.env` file by listing the environment variables: + ```bash export WORKOS_API_KEY= export WORKOS_CLIENT_ID= @@ -59,11 +66,13 @@ An example Django application demonstrating how to use the [WorkOS Python SDK](h To exit the Nano text editor, type `CTRL + x`. When prompted to "Save modified buffer", type `Y`, then press the `Enter` or `Return` key. 8. Source the environment variables so they are accessible to the operating system. + ```bash (env) $ source .env ``` You can ensure the environment variables were set correctly by running the following commands. The output should match the corresponding values. + ```bash (env) $ echo $WORKOS_API_KEY (env) $ echo $WORKOS_CLIENT_ID @@ -71,11 +80,13 @@ An example Django application demonstrating how to use the [WorkOS Python SDK](h ``` 9. Run the Django migrations. Again, ensure you're in the `python-django-sso-example/` directory where the `manange.py` file is. + ```bash (env) $ python3 manage.py migrate ``` You should see output like: + ```bash Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions @@ -85,30 +96,30 @@ An example Django application demonstrating how to use the [WorkOS Python SDK](h . . . ``` -10. In `python-django-sso-example/sso/views.py` change the `CONNECTION_ID` string value to the connection ID that you are targeting. This can be found in the WorkOS Dashboard under the Connection Settings. +10. In `python-django-sso-example/sso/views.py` change the `ORGANIZATION_ID` string value to the organization ID that you are targeting. This can be found in the WorkOS Dashboard under the Organization Settings. 11. The final setup step is to start the server. - ```bash - (env) $ python3 manage.py runserver --insecure - ``` - You'll know the server is running when you see no warnings or errors in the CLI, and output similar to the following is displayed: +```bash +(env) $ python3 manage.py runserver --insecure +``` - ```bash - Watching for file changes with StatReloader - Performing system checks... - - System check identified no issues (0 silenced). - March 18, 2021 - 04:54:50 - Django version 3.1.7, using settings 'workos_django.settings' - Starting development server at http://127.0.0.1:8000/ - Quit the server with CONTROL-C. - ``` +You'll know the server is running when you see no warnings or errors in the CLI, and output similar to the following is displayed: + +```bash +Watching for file changes with StatReloader +Performing system checks... - Navigate to `localhost:8000` in your web browser. You should see a "Login" link. If you click this link, you'll be redirected to an HTTP `404` page because we haven't set up SSO yet! +System check identified no issues (0 silenced). +March 18, 2021 - 04:54:50 +Django version 3.1.7, using settings 'workos_django.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. +``` - You can stop the local Django server for now by entering `CTRL + c` on the command line. +Navigate to `localhost:8000` in your web browser. You should see a "Login" link. If you click this link, you'll be redirected to an HTTP `404` page because we haven't set up SSO yet! +You can stop the local Django server for now by entering `CTRL + c` on the command line. ## SSO Setup with WorkOS @@ -122,15 +133,15 @@ If you get stuck, please reach out to us at support@workos.com so we can help. 12. Naviagte to the `python-django-sso-example` directory, which contains the `manage.py` file. Source the virtual environment we created earlier, if it isn't still activated from the steps above. Start the Django server locally. - ```bash - $ cd ~/Desktop/python-django-sso-example/ - $ source env/bin/activate - (env) $ python3 manage.py runserver - ``` +```bash +$ cd ~/Desktop/python-django-sso-example/ +$ source env/bin/activate +(env) $ python3 manage.py runserver +``` - Once running, navigate to http://localhost:8000 to test out the SSO workflow. +Once running, navigate to http://localhost:8000 to test out the SSO workflow. - Hooray! +Hooray! ## Need help? diff --git a/python-django-sso-example/sso/static/css/login.css b/python-django-sso-example/sso/static/css/login.css index fdcd954..cf97cd6 100644 --- a/python-django-sso-example/sso/static/css/login.css +++ b/python-django-sso-example/sso/static/css/login.css @@ -1,253 +1,385 @@ body { - font-family: Inter, sans-serif; - background-color: #f9f9fb; - + font-family: Inter, sans-serif; + background-color: #f9f9fb; } .container_login { - display: flex; - flex-direction: column; - justify-content: center; - margin: auto; - width: 30%; - height: 90vh; + display: flex; + flex-direction: column; + justify-content: center; + margin: auto; + width: 30%; + height: 90vh; } .container_login img { - height: 150px; + height: 150px; } .container_login h1 { - font-size: 65px; - color: #111111; - position: relative; - bottom: 10px; + font-size: 65px; + color: #111111; + position: relative; + bottom: 10px; } .flex { - display: flex; - justify-content: center; + display: flex; + justify-content: center; + align-items: center; } .flex_column { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; } -.space-between { - justify-content: space-between; +.m-top-20 { + margin-top: 20px; } .width-75 { - width: 75%; + width: 75%; } .width-40vw { - width: 40vw; + width: 40vw; +} + +.width-25vw { + width: 25vw; +} + +.width-18vw { + width: 18vw; +} + +.width-941px { + width: 941px; +} + +.width-335 { + width: 335px; +} + +.height-315 { + height: 315px; +} + +.height-40vh { + height: 40vw; +} + +.height-80vh { + height: 80vh; +} + +.height-100vh { + height: 100vh; +} + +.space-between { + justify-content: space-between; } .container_success { - display: flex; - flex-direction: column; - justify-content: center; - margin: auto; - width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + margin: auto; + width: 100%; } .heading_div { - margin: 25px 0px 25px 0px; + margin: 35px 0px 0px 0px; } .heading_text_div { - align-self: center; + align-self: center; } .container_success { - display: flex; - flex-direction: column; - justify-content: center; - margin: auto; - width: 100%; - background-size: cover; + display: flex; + flex-direction: column; + justify-content: center; + margin: auto; + width: 100%; + background-size: cover; +} + +.text_input { + border: 1px solid #555555; + border-radius: 10px; + margin: 10px 0px 7px 0px; + padding: 5px; + height: 35px; + width: 18vw; + text-align: center; +} + +.code-input { + width: 75px; + height: 100px; + margin: 0px 5px 30px 5px; + font-size: 60px; + color: darkslategray; +} + +.qr_div { + align-self: center; + margin-top: 45px; +} + +.qr_code { + width: 7vw; + max-width: 100px; +} + +.factor_card { + border: 1px solid #555555; + border-radius: 10px; + width: 20vw; + margin: 0px 15px 0px 15px; + padding: 25px; +} + +.card { + border: 1px solid #555555; + border-radius: 10px; + margin: 0px 15px 0px 15px; + padding: 25px 50px; + margin-bottom: 20px; +} + +.profile_card { + width: 40vw; + overflow: scroll; + background: white; + box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1); } .button { - background-color: #6363f1; - border: 2px solid #6363f1; - border-radius: 26px; - color: white; - padding: 16px 32px; - text-align: center; - text-decoration: none; - display: inline-block; - font-size: 16px; - margin: 4px 2px; - transition-duration: 0.4s; - cursor: pointer; - box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1); + background-color: #6363f1; + border: 2px solid #6363f1; + border-radius: 10px; + color: white; + padding: 8px 16px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + margin: 4px 2px; + transition-duration: 0.4s; + cursor: pointer; + box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1); } .button-outline { - background-color: #f9f9fb; - color: #6363f1; - padding: 8px 16px; + background-color: #f9f9fb; + color: #6363f1; + padding: 8px 16px; +} + +.button-sm { + padding: 8px 16px; } .button:hover, .button-outline:hover { - background-color: #555555; - border: 2px solid #555555; - color: white; + background-color: #555555; + border: 2px solid #555555; + color: white; } .sales-button { - margin-left: 10px; + margin-left: 10px; } .login_button { - width: 95%; - box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1); + width: 100%; + box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1); + background-color: white; + color: ; +} + +.login_button:hover { + border-color: #6363f1; + color: #292929; } h2, h1 { - text-align: center; - color: #555555; + text-align: center; + color: #555555; } .logged_in_div_right { - width: 60%; - height: 90vh; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - position: relative; - bottom: 10%; - /* background-color: #f9f9fb; */ + width: 60%; + height: 90vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: relative; + bottom: 10%; } .logged_in_div_left { - width: 40%; - height: 100vh; - display: flex; - flex-direction: column; - justify-content: center; - align-items: left; - background-color: #f9f9fb; - margin-left: 4vw; + width: 40%; + height: 100vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: left; + background-color: #f9f9fb; + margin-left: 4vw; } .logged_in_div_left div { - justify-content: left; - position: relative; - bottom: 20%; + justify-content: left; + position: relative; + bottom: 20%; } .logged_in_div_left h1 { - color: #111111; - font-size: 75px; - text-align: left; - margin-bottom: 0px; - font-weight: normal; - letter-spacing: -.05em; + color: #111111; + font-size: 75px; + text-align: left; + margin-bottom: 0px; + font-weight: normal; + letter-spacing: -0.05em; } .home-hero-gradient { - background-image: linear-gradient(45deg, #a163f1, #6363f1 22%, #3498ea 40%, #40dfa3 67%, rgba(64, 223, 163, 0)); - background-size: 150% 100%; - background-repeat: no-repeat; - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - animation: intro-gradient 1.2s cubic-bezier(0.85, 0.26, 0.89, 0.93); - animation-iteration-count: 1; - animation-fill-mode: backwards; - animation-delay: 0.4s; - text-align: left; - font-size: 75px; - letter-spacing: -.05em; - font-weight: normal; - margin-top: 0px; + background-image: linear-gradient( + 45deg, + #a163f1, + #6363f1 22%, + #3498ea 40%, + #40dfa3 67%, + rgba(64, 223, 163, 0) + ); + background-size: 150% 100%; + background-repeat: no-repeat; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: intro-gradient 1.2s cubic-bezier(0.85, 0.26, 0.89, 0.93); + animation-iteration-count: 1; + animation-fill-mode: backwards; + animation-delay: 0.4s; + text-align: left; + font-size: 75px; + letter-spacing: -0.05em; + font-weight: normal; + margin-top: 0px; } .title-text { - margin-bottom: -50px; + margin-bottom: -50px; } .title-subtext { - color: gray; - line-height: 10px; - margin-bottom: 15px; - font-weight: 200; + color: gray; + line-height: 10px; + margin-bottom: 15px; + font-weight: 200; } .logged_in_div_left button { - padding: 8px 22px; - box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1); -} - -div.text_box { - background-color: #f9f9fb; - width: 40vw; - max-height: 30vh; - padding: 10px; - word-wrap: break-word; - overflow: scroll; - border-width: 3px; - border-style: solid; - border-image: - linear-gradient(#a163f1, #6363f1 22%, #3498ea 40%, #40dfa3 67%, rgba(64, 223, 163, 0)) 0 100%; + padding: 8px 22px; + box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1); } .logged_in_nav { - display: flex; - justify-content: space-between; - background-color: #f9f9fb; - height: 60px; - padding: 15px 30px 15px 30px; - - z-index: 1000; + display: flex; + justify-content: space-between; + background-color: #f9f9fb; + height: 60px; + padding: 15px 30px 15px 30px; + z-index: 1000; + width: 95vw; } .logged_in_nav p { - padding: 4px 0px 0px 15px; - line-height: 1; - color: #29363d; + padding: 4px 0px 0px 15px; + line-height: 1; + color: #29363d; } .logged_in_nav img { - height: 50px; - border-radius: 50%; - border: 2px solid #2f2e2e; -} - -.logged_in_nav img:hover { - border: 2px solid #555555; + height: 50px; } .nav-item { - color: black; - border: 2px solid #f9f9fb; - background-color: #f9f9fb; - box-shadow: none; - border-radius: 5px; + color: black; + border: 2px solid #f9f9fb; + background-color: #f9f9fb; + box-shadow: none; + border-radius: 5px; } .blog-nav-button { - margin-right: 20px; - background-color: #f9f9fb; - border: 2px solid #f9f9fb; + margin-right: 20px; + background-color: #f9f9fb; + border: 2px solid #f9f9fb; } .nav-item:hover { - background-color: #f9f9fb; - border: 2px solid #f9f9fb; - color: #a6a4a4; + background-color: #f9f9fb; + border: 2px solid #f9f9fb; + color: #a6a4a4; +} + +.workos-logo { + position: relative; + top: 38px; + padding-right: 25px; + height: 75px !important; +} + +.webhooks_container { + width: 45vw; + padding: 25px; + max-height: 450px; + overflow-y: scroll; } -pre.prettyprint { - border: none !important; -} \ No newline at end of file +.mb-0 { + margin-bottom: 0px; +} + +.mb-20 { + margin-botton: 20px; +} + +.google_button { + background-image: url("../images/google-button.png"); + background-size: cover; +} + +.microsoft_button { + background-image: url("../images/microsoft-button.png"); + background-size: cover; +} + +.saml_button { + background-image: url("../images/saml-button.png"); + background-size: cover; +} + +.error_message { + color: #6363f1; + margin-top: 0px; + font-size: 12px; +} + +#noborder { + border: none; +} + +#noborder > :first-child { + display: none; +} diff --git a/python-django-sso-example/sso/static/images/google-button.png b/python-django-sso-example/sso/static/images/google-button.png new file mode 100644 index 0000000..0332ec0 Binary files /dev/null and b/python-django-sso-example/sso/static/images/google-button.png differ diff --git a/python-django-sso-example/sso/static/images/microsoft-button.png b/python-django-sso-example/sso/static/images/microsoft-button.png new file mode 100644 index 0000000..2c725dc Binary files /dev/null and b/python-django-sso-example/sso/static/images/microsoft-button.png differ diff --git a/python-django-sso-example/sso/static/images/saml-button.png b/python-django-sso-example/sso/static/images/saml-button.png new file mode 100644 index 0000000..e03328f Binary files /dev/null and b/python-django-sso-example/sso/static/images/saml-button.png differ diff --git a/python-django-sso-example/sso/templates/sso/login.html b/python-django-sso-example/sso/templates/sso/login.html index f316bfe..709fe0f 100644 --- a/python-django-sso-example/sso/templates/sso/login.html +++ b/python-django-sso-example/sso/templates/sso/login.html @@ -7,15 +7,47 @@ - -
-
- workos logo -
+ +
+
+
+ workos logo +
-

Python Django SSO Example App

- +
+
+
+
+
+
+ {% csrf_token %} +
+
+ Log in with SSO +
+
+ + + +
+
+
+
+
\ No newline at end of file diff --git a/python-django-sso-example/sso/templates/sso/login_successful.html b/python-django-sso-example/sso/templates/sso/login_successful.html index ed3c9a9..ddf78c9 100644 --- a/python-django-sso-example/sso/templates/sso/login_successful.html +++ b/python-django-sso-example/sso/templates/sso/login_successful.html @@ -8,13 +8,11 @@
- workos logo -
-
-

You're logged in {{first_name}}, welcome!

+ workos logo
+
-
+
@@ -23,39 +21,29 @@
+
-
-
-

Your app,

-

Enterprise Ready

-
-
-

Start selling to enterprise customers with just a few lines of code.

-

Implement features like single sign-on in minutes instead of months.

-
- -
+
-

Raw Profile Response Details

-
-
-
+                
+
+

Profile Details

+
+
+ +
+
+
+
                         {{raw_profile}}
                     
-
- \ No newline at end of file diff --git a/python-django-sso-example/sso/views.py b/python-django-sso-example/sso/views.py index aea2eb7..fbefb7c 100644 --- a/python-django-sso-example/sso/views.py +++ b/python-django-sso-example/sso/views.py @@ -3,6 +3,7 @@ import json from django.conf import settings from django.shortcuts import redirect, render +from django.urls import reverse workos.api_key = os.getenv("WORKOS_API_KEY") @@ -16,9 +17,9 @@ ) # Constants -# Required: Fill in CONNECTION_ID for the desired connection from the WorkOS Dashboard +# Required: Fill in CUSTOMER_ORGANIZATION_ID for the desired organization from the WorkOS Dashboard -CONNECTION_ID = "xxx" +CUSTOMER_ORGANIZATION_ID = "xxx" REDIRECT_URI = os.getenv("REDIRECT_URI") @@ -39,11 +40,17 @@ def login(request): def auth(request): - authorization_url = workos.client.sso.get_authorization_url( - connection=CONNECTION_ID, - redirect_uri=REDIRECT_URI, - state="state_can_be_any_string", - ) + + login_type = request.POST["login_method"] + params = {"redirect_uri": REDIRECT_URI, "state": {}} + + if login_type == "saml": + params["organization"] = CUSTOMER_ORGANIZATION_ID + else: + params["provider"] = login_type + + authorization_url = workos.client.sso.get_authorization_url(**params) + return redirect(authorization_url)