Add refresh token button
This commit is contained in:
parent
ff72f0cae5
commit
923a63f5d5
6 changed files with 58 additions and 24 deletions
|
@ -13,7 +13,7 @@ from httpx import AsyncClient
|
|||
from authlib.oauth2.auth import OAuth2Token
|
||||
|
||||
from .models import User
|
||||
from .database import TokenNotInDb, db, UserNotInDB
|
||||
from .database import db, TokenNotInDb, UserNotInDB
|
||||
from .settings import oidc_providers_settings
|
||||
|
||||
logger = logging.getLogger("oidc-test")
|
||||
|
@ -36,14 +36,14 @@ async def fetch_token(name, request):
|
|||
|
||||
|
||||
async def update_token(name, token, refresh_token=None, access_token=None):
|
||||
"""Update the token in the database"""
|
||||
oidc_provider_settings = oidc_providers_settings[name]
|
||||
sid: str = oidc_provider_settings.decode(token["id_token"])["sid"]
|
||||
item = await db.get_token(oidc_provider_settings, sid)
|
||||
# update old token
|
||||
if access_token is not None:
|
||||
item["access_token"] = token.get("access_token")
|
||||
if refresh_token is not None:
|
||||
item["refresh_token"] = refresh_token
|
||||
item["access_token"] = token["access_token"]
|
||||
item["refresh_token"] = token["refresh_token"]
|
||||
item["id_token"] = token["id_token"]
|
||||
item["expires_at"] = token["expires_at"]
|
||||
logger.info(f"Token {sid} refreshed")
|
||||
# It's a fake db and only in memory, so there's nothing to save
|
||||
|
@ -70,8 +70,8 @@ def init_providers():
|
|||
api_base_url=provider.url,
|
||||
# For PKCE (not implemented yet):
|
||||
# code_challenge_method="S256",
|
||||
# fetch_token=fetch_token,
|
||||
# update_token=update_token,
|
||||
fetch_token=fetch_token,
|
||||
update_token=update_token,
|
||||
# client_id="some-client-id", # if enabled, authlib will also check that the access token belongs to this client id (audience)
|
||||
)
|
||||
|
||||
|
@ -101,7 +101,10 @@ def get_oidc_provider_or_none(request: Request) -> StarletteOAuth2App | None:
|
|||
|
||||
def get_oidc_provider(request: Request) -> StarletteOAuth2App:
|
||||
if (oidc_provider := get_oidc_provider_or_none(request)) is None:
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "No such provider")
|
||||
if oidc_provider is None:
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "No provider")
|
||||
else:
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "No such provider")
|
||||
else:
|
||||
return oidc_provider
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@ from .auth_utils import (
|
|||
authlib_oauth,
|
||||
get_providers_info,
|
||||
get_token_or_none,
|
||||
get_token,
|
||||
update_token,
|
||||
)
|
||||
from .auth_misc import pretty_details
|
||||
from .database import TokenNotInDb, db
|
||||
|
@ -97,7 +99,7 @@ async def home(
|
|||
access_token_scope = None
|
||||
else:
|
||||
try:
|
||||
access_token_scope = user.decode_access_token()["scope"]
|
||||
access_token_scope = user.get_scope(verify_signature=False)
|
||||
except InvalidTokenError as err:
|
||||
access_token_scope = None
|
||||
logger.info("Invalid token")
|
||||
|
@ -113,15 +115,22 @@ async def home(
|
|||
"resources": resources,
|
||||
}
|
||||
if token is None:
|
||||
context["access_token"] = None
|
||||
context["id_token_parsed"] = None
|
||||
context["access_token_parsed"] = None
|
||||
context["refresh_token_parsed"] = None
|
||||
else:
|
||||
context["access_token"] = token["access_token"]
|
||||
assert oidc_provider is not None
|
||||
assert oidc_provider.name is not None
|
||||
oidc_provider_settings = oidc_providers_settings[oidc_provider.name]
|
||||
context["id_token_parsed"] = pretty_details(user, now)
|
||||
context["access_token_parsed"] = oidc_provider_settings.decode(token["access_token"])
|
||||
# context["id_token_parsed"] = pretty_details(user, now)
|
||||
context["id_token_parsed"] = oidc_provider_settings.decode(
|
||||
token["id_token"], verify_signature=False
|
||||
)
|
||||
context["access_token_parsed"] = oidc_provider_settings.decode(
|
||||
token["access_token"], verify_signature=False
|
||||
)
|
||||
context["refresh_token_parsed"] = oidc_provider_settings.decode(
|
||||
token["refresh_token"], verify_signature=False
|
||||
)
|
||||
|
@ -282,6 +291,21 @@ async def non_compliant_logout(
|
|||
)
|
||||
|
||||
|
||||
@app.get("/refresh")
|
||||
async def refresh(
|
||||
request: Request,
|
||||
oidc_provider: Annotated[StarletteOAuth2App, Depends(get_oidc_provider)],
|
||||
token: Annotated[OAuth2Token, Depends(get_token)],
|
||||
) -> RedirectResponse:
|
||||
"""Manually refresh token"""
|
||||
new_token = await oidc_provider.fetch_access_token(
|
||||
refresh_token=token["refresh_token"],
|
||||
grant_type="refresh_token",
|
||||
)
|
||||
await update_token(oidc_provider.name, new_token)
|
||||
return RedirectResponse(url=request.url_for("home"))
|
||||
|
||||
|
||||
# Snippet for running standalone
|
||||
# Mostly useful for the --version option,
|
||||
# as running with uvicorn is easy and provides better flexibility, eg.
|
||||
|
|
|
@ -54,10 +54,15 @@ class User(UserBase):
|
|||
access_token_scopes = []
|
||||
return scope in set(info_scopes + access_token_scopes)
|
||||
|
||||
def decode_access_token(self):
|
||||
def decode_access_token(self, verify_signature: bool = True):
|
||||
assert self.access_token is not None
|
||||
assert self.oidc_provider is not None
|
||||
assert self.oidc_provider.name is not None
|
||||
from .settings import oidc_providers_settings
|
||||
|
||||
return oidc_providers_settings[self.oidc_provider.name].decode(self.access_token)
|
||||
return oidc_providers_settings[self.oidc_provider.name].decode(
|
||||
self.access_token, verify_signature=verify_signature
|
||||
)
|
||||
|
||||
def get_scope(self, verify_signature: bool = True):
|
||||
return self.decode_access_token(verify_signature=verify_signature)["scope"]
|
||||
|
|
|
@ -2,6 +2,7 @@ async function checkHref(elem, token, authProvider) {
|
|||
const msg = document.getElementById("msg")
|
||||
const url = `resource/${elem.getAttribute("resource-id")}`
|
||||
const resp = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: new Headers({
|
||||
"Content-type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<link href="{{ url_for('static', path='/styles.css') }}" rel="stylesheet">
|
||||
<script src="{{ url_for('static', path='/utils.js') }}"></script>
|
||||
</head>
|
||||
<body onload="checkPerms('links-to-check', '{{ user.access_token }}', '{{ oidc_provider_settings.id }}')">
|
||||
<body onload="checkPerms('links-to-check', '{{ access_token }}', '{{ oidc_provider_settings.id }}')">
|
||||
<h1>OIDC-test - FastAPI client</h1>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
Account management
|
||||
</button>
|
||||
{% endif %}
|
||||
<button onclick="location.href='{{ request.url_for("refresh") }}'" class="refresh">Refresh</button>
|
||||
<button onclick="location.href='{{ request.url_for("logout") }}'" class="logout">Logout</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -66,21 +67,21 @@
|
|||
Resources validated by scope:
|
||||
</p>
|
||||
<div class="links-to-check">
|
||||
<button resource-id="time" onclick="get_resource('time', '{{ user.access_token }}', '{{ oidc_provider_settings.id }}')">Time</button>
|
||||
<button resource-id="bs" onclick="get_resource('bs', '{{ user.access_token }}', '{{ oidc_provider_settings.id }}')">BS</button>
|
||||
<button resource-id="time" onclick="get_resource('time', '{{ access_token }}', '{{ oidc_provider_settings.id }}')">Time</button>
|
||||
<button resource-id="bs" onclick="get_resource('bs', '{{ access_token }}', '{{ oidc_provider_settings.id }}')">BS</button>
|
||||
</div>
|
||||
<p>
|
||||
Resources validated by role:
|
||||
</p>
|
||||
<div class="links-to-check">
|
||||
<button resource-id="public" onclick="get_resource('public', '{{ user.access_token }}', '{{ oidc_provider_settings.id }}')">Public</button>
|
||||
<button resource-id="protected" onclick="get_resource('protected', '{{ user.access_token }}', '{{ oidc_provider_settings.id }}')">Auth protected content</button>
|
||||
<button resource-id="protected-by-foorole" onclick="get_resource('protected-by-foorole', '{{ user.access_token }}', '{{ oidc_provider_settings.id }}')">Auth + foorole protected content</button>
|
||||
<button resource-id="protected-by-foorole-or-barrole" onclick="get_resource('protected-by-foorole-or-barrole', '{{ user.access_token }}', '{{ oidc_provider_settings.id }}')">Auth + foorole or barrole protected content</button>
|
||||
<button resource-id="protected-by-barrole" onclick="get_resource('protected-by-barrole', '{{ user.access_token }}', '{{ oidc_provider_settings.id }}')">Auth + barrole protected content</button>
|
||||
<button resource-id="protected-by-foorole-and-barrole" onclick="get_resource('protected-by-foorole-and-barrole', '{{ user.access_token }}', '{{ oidc_provider_settings.id }}')">Auth + foorole and barrole protected content</button>
|
||||
<button resource-id="fast_api_depends" class="hidden" onclick="get_resource('fast_api_depends', '{{ user.access_token }}', '{{ oidc_provider_settings.id }}')">Using FastAPI Depends</button>
|
||||
<!--<button resource-id="introspect" onclick="get_resource('introspect', '{{ user.access_token }}', '{{ oidc_provider_settings.id }}')">Introspect token (401 expected)</button>-->
|
||||
<button resource-id="public" onclick="get_resource('public', '{{ access_token }}', '{{ oidc_provider_settings.id }}')">Public</button>
|
||||
<button resource-id="protected" onclick="get_resource('protected', '{{ access_token }}', '{{ oidc_provider_settings.id }}')">Auth protected content</button>
|
||||
<button resource-id="protected-by-foorole" onclick="get_resource('protected-by-foorole', '{{ access_token }}', '{{ oidc_provider_settings.id }}')">Auth + foorole protected content</button>
|
||||
<button resource-id="protected-by-foorole-or-barrole" onclick="get_resource('protected-by-foorole-or-barrole', '{{ access_token }}', '{{ oidc_provider_settings.id }}')">Auth + foorole or barrole protected content</button>
|
||||
<button resource-id="protected-by-barrole" onclick="get_resource('protected-by-barrole', '{{ access_token }}', '{{ oidc_provider_settings.id }}')">Auth + barrole protected content</button>
|
||||
<button resource-id="protected-by-foorole-and-barrole" onclick="get_resource('protected-by-foorole-and-barrole', '{{ access_token }}', '{{ oidc_provider_settings.id }}')">Auth + foorole and barrole protected content</button>
|
||||
<button resource-id="fast_api_depends" class="hidden" onclick="get_resource('fast_api_depends', '{{ access_token }}', '{{ oidc_provider_settings.id }}')">Using FastAPI Depends</button>
|
||||
<!--<button resource-id="introspect" onclick="get_resource('introspect', '{{ access_token }}', '{{ oidc_provider_settings.id }}')">Introspect token (401 expected)</button>-->
|
||||
</div>
|
||||
<div class="resourceResult">
|
||||
<div id="resource" class="resource"></div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue