Skip to content

Commit 38fc7ff

Browse files
authored
Merge pull request #1 from oktadev/basic-authentication
Adds authentication with Auth0
2 parents 13005da + 48c0d3f commit 38fc7ff

14 files changed

+150
-20
lines changed

auth/__init__.py

Whitespace-only changes.

auth/config.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from authlib.integrations.starlette_client import OAuth
2+
from config import config
3+
4+
5+
"""Storing the configuration into the `auth0_config` variable for later usage"""
6+
7+
auth0_config = config['AUTH0']
8+
9+
oauth = OAuth()
10+
oauth.register(
11+
"auth0",
12+
client_id=auth0_config['CLIENT_ID'],
13+
client_secret=auth0_config['CLIENT_SECRET'],
14+
client_kwargs={
15+
"scope": "openid profile email",
16+
},
17+
server_metadata_url=f'https://{auth0_config["DOMAIN"]}/.well-known/openid-configuration'
18+
)

auth/dependencies.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from fastapi import Request, HTTPException, status
2+
3+
4+
def protected_endpoint(request: Request):
5+
"""
6+
This Dependency protects an endpoint and it can only be accessed if the user has an active session
7+
"""
8+
if 'id_token' not in request.session: # it could be userinfo instead of id_token
9+
# this will redirect people to the login after if they are not logged in
10+
raise HTTPException(
11+
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
12+
detail="Not authorized",
13+
headers={
14+
"Location": "/login"
15+
}
16+
)

auth/routes.py

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from urllib.parse import quote_plus, urlencode
2+
3+
from fastapi import APIRouter, Request
4+
from fastapi.responses import RedirectResponse
5+
6+
from auth.config import auth0_config, oauth
7+
8+
9+
auth_router = APIRouter()
10+
11+
12+
@auth_router.get("/login")
13+
async def login(request: Request):
14+
"""
15+
Redirects the user to the Auth0 Universal Login (https://auth0.com/docs/authenticate/login/auth0-universal-login)
16+
"""
17+
if 'id_token' not in request.session: # it could be userinfo instead of id_token
18+
return await oauth.auth0.authorize_redirect(
19+
request,
20+
redirect_uri=request.url_for("callback")
21+
)
22+
return RedirectResponse(url=request.url_for("profile"))
23+
24+
25+
@auth_router.get("/signup")
26+
async def signup(request: Request):
27+
"""
28+
Redirects the user to the Auth0 Universal Login (https://auth0.com/docs/authenticate/login/auth0-universal-login)
29+
"""
30+
if 'id_token' not in request.session: # it could be userinfo instead of id_token
31+
return await oauth.auth0.authorize_redirect(
32+
request,
33+
redirect_uri=request.url_for("callback"),
34+
screen_hint="signup"
35+
)
36+
return RedirectResponse(url=request.url_for("profile"))
37+
38+
39+
@auth_router.get("/logout")
40+
def logout(request: Request):
41+
"""
42+
Redirects the user to the Auth0 Universal Login (https://auth0.com/docs/authenticate/login/auth0-universal-login)
43+
"""
44+
response = RedirectResponse(
45+
url="https://" + auth0_config['DOMAIN']
46+
+ "/v2/logout?"
47+
+ urlencode(
48+
{
49+
"returnTo": request.url_for("home"),
50+
"client_id": auth0_config['CLIENT_ID'],
51+
},
52+
quote_via=quote_plus,
53+
)
54+
)
55+
request.session.clear()
56+
return response
57+
58+
59+
@auth_router.get("/callback")
60+
async def callback(request: Request):
61+
"""
62+
Callback redirect from Auth0
63+
"""
64+
token = await oauth.auth0.authorize_access_token(request)
65+
# Store `id_token`, and `userinfo` in session
66+
request.session['id_token'] = token['id_token']
67+
request.session['userinfo'] = token['userinfo']
68+
return RedirectResponse(url=request.url_for("profile"))

config.py

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from fastapi import FastAPI, Request
44
from fastapi.staticfiles import StaticFiles
55
from fastapi.templating import Jinja2Templates
6+
from starlette.middleware.sessions import SessionMiddleware
67

78
from utils import to_pretty_json
89

@@ -16,6 +17,9 @@ def load_config():
1617
return config
1718

1819

20+
config = load_config()
21+
22+
1923
def create_templates():
2024
templates = Jinja2Templates(directory="templates")
2125
templates.env.filters['to_pretty_json'] = to_pretty_json
@@ -28,6 +32,8 @@ def create_templates():
2832
def create_app():
2933
app = FastAPI()
3034
app.mount("/static", StaticFiles(directory="static"), name="static")
35+
# You need this to save temporary code & state in session
36+
app.add_middleware(SessionMiddleware, secret_key=config['WEBAPP']['SESSION_SECRET'])
3137
return app
3238

3339
app = create_app()

main.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from config import app
22

3+
from auth.routes import auth_router
34
from webapp.routes import webapp_router
45

56

7+
app.include_router(auth_router)
68
app.include_router(webapp_router)

templates/navigation/desktop/nav_bar.html

+1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
<nav class="nav-bar">
33
{% include 'navigation/desktop/nav_bar_brand.html' %}
44
{% include 'navigation/desktop/nav_bar_tabs.html' %}
5+
{% include 'navigation/desktop/nav_bar_buttons.html' %}
56
</nav>
67
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<div class="nav-bar__buttons">
2+
{% if request.session %}
3+
<a href="{{ url_for('logout') }}" class="button__logout">Log Out</a>
4+
{% else %}
5+
<a href="{{ url_for('login') }}" class="button__login" >Log In</a>
6+
<a href="{{ url_for('signup') }}" class="button__sign-up" >Sign Up</a>
7+
{% endif %}
8+
</div>

templates/navigation/desktop/nav_bar_tabs.html

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
<div class="nav-bar__tabs">
1111
{{ tab(label="Profile", path=url_for('profile')) }}
1212
{{ tab(label="Public", path=url_for('public')) }}
13-
{{ tab(label="Protected", path=url_for('protected')) }}
14-
{{ tab(label="Admin", path=url_for('admin')) }}
13+
{% if request.session %}
14+
{{ tab(label="Protected", path=url_for('protected')) }}
15+
{{ tab(label="Admin", path=url_for('admin')) }}
16+
{% endif %}
1517
</div>

templates/navigation/mobile/mobile_nav_bar.html

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
{% include 'navigation/mobile/menu_button.html' %}
55
<div id="mobile-menu" class="mobile-nav-bar__menu mobile-nav-bar__menu--closed">
66
{% include 'navigation/mobile/mobile_nav_bar_tabs.html' %}
7+
{% include 'navigation/mobile/mobile_nav_bar_buttons.html' %}
78
</div>
89
</nav>
910
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<div class="mobile-nav-bar__buttons">
2+
{% if request.session %}
3+
<a href="{{ url_for('logout') }}" class="button__logout">Log Out</a>
4+
{% else %}
5+
<a href="{{ url_for('login') }}" class="button__login" >Log In</a>
6+
<a href="{{ url_for('signup') }}" class="button__sign-up" >Sign Up</a>
7+
{% endif %}
8+
</div>

templates/navigation/mobile/mobile_nav_bar_tabs.html

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
<div class="mobile-nav-bar__tabs">
1111
{{ tab(label="Profile", path=url_for('profile')) }}
1212
{{ tab(label="Public", path=url_for('public')) }}
13-
{{ tab(label="Protected", path=url_for('protected')) }}
14-
{{ tab(label="Admin", path=url_for('admin')) }}
13+
{% if request.session %}
14+
{{ tab(label="Protected", path=url_for('protected')) }}
15+
{{ tab(label="Admin", path=url_for('admin')) }}
16+
{% endif %}
1517
</div>

templates/profile.html

+8-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
<h1 id="page-title" class="content__title">Profile Page</h1>
77
<div class="content__body">
88
<p id="page-description">
9-
<span><strong>Only authenticated users should access this page.</strong></span>
9+
<span>
10+
You can use the <strong>ID Token</strong> to get the profile information of an
11+
authenticated user.
12+
</span>
13+
<span>
14+
<strong>Only authenticated users can access this page.</strong>
15+
</span>
1016
</p>
1117
<div class="profile-grid">
1218
<div class="profile__header">
@@ -18,7 +24,7 @@ <h2 class="profile__title">{{ userinfo['name'] }}</h2>
1824
</div>
1925

2026
<div class="profile__details">
21-
{{ code_snippet(title="User Profile Object", code=userinfo) }}
27+
{{ code_snippet(title="Decoded ID Token", code=userinfo) }}
2228
</div>
2329
</div>
2430
</div>

webapp/routes.py

+6-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from fastapi import APIRouter, Request
1+
from fastapi import APIRouter, Depends, Request
22

3+
from auth.dependencies import protected_endpoint
34
from config import templates
45
from services.message_service import MessageService
56

@@ -52,25 +53,16 @@ def home(request: Request):
5253
)
5354

5455

55-
@webapp_router.get("/profile")
56+
@webapp_router.get("/profile", dependencies=[Depends(protected_endpoint)])
5657
def profile(request: Request):
5758
"""
5859
Profile endpoint, should only be accessible after login
5960
"""
60-
user_info = {
61-
"nickname": "Customer",
62-
"name": "One Customer",
63-
"picture": "https://cdn.auth0.com/blog/hello-auth0/auth0-user.png",
64-
"updated_at": "2021-05-04T21:33:09.415Z",
65-
"email": "[email protected]",
66-
"email_verified": False,
67-
"sub": "auth0|12345678901234567890"
68-
}
6961
return templates.TemplateResponse(
7062
"profile.html",
7163
{
7264
"request": request,
73-
"userinfo": user_info
65+
"userinfo": request.session['userinfo']
7466
}
7567
)
7668

@@ -89,7 +81,7 @@ def public(request: Request):
8981
)
9082

9183

92-
@webapp_router.get("/protected")
84+
@webapp_router.get("/protected", dependencies=[Depends(protected_endpoint)])
9385
def protected(request: Request):
9486
"""
9587
Protected endpoint, should only be accessible after login
@@ -103,7 +95,7 @@ def protected(request: Request):
10395
)
10496

10597

106-
@webapp_router.get("/admin")
98+
@webapp_router.get("/admin", dependencies=[Depends(protected_endpoint)])
10799
def admin(request: Request):
108100
"""
109101
Admin endpoint, should only be accessible after login if the user has the admin permissions

0 commit comments

Comments
 (0)