Skip to content

Commit

Permalink
fix: custom stylesheet and js are not loaded after OIDC authentication
Browse files Browse the repository at this point in the history
* fix: allow access to assets and static endpoint
* docs: improve details about authentication
  • Loading branch information
FabienArcellier committed Aug 30, 2024
1 parent 9d3783d commit 2d23b3c
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 11 deletions.
23 changes: 23 additions & 0 deletions docs/framework/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ The Writer Framework authentication module allows you to restrict access to your
trigger authentication for certain pages exclusively.
</Warning>

<Warning>
Static assets from Writer Framework exposed through `/static` and `/extensions` endpoints are not protected behind Authentication.
</Warning>

## Use Basic Auth

Basic Auth is a simple authentication method that uses a username and password. Authentication configuration is done in the [server_setup.py module](/framework/custom-server).
Expand Down Expand Up @@ -134,6 +138,25 @@ writer.serve.register_auth(oidc)

<img src="/framework/images/authentication_oidc.png" />

### Custom static assets

Static assets in your application are inaccessible. You can use the `public_static_assets` parameter to allow their usage.

<Info>You have to declare [frontend-script](./frontend-scripts), images, ... as public as they are loaded directly by the browser.</Info>

```python
oidc = writer.auth.Auth0(
client_id="xxxxxxx",
client_secret="xxxxxxxxxxxxx",
domain="xxx-xxxxx.eu.auth0.com",
host_url=os.getenv('HOST_URL', "http://localhost:5000"),
public_static_assets=[
'/assets', # make public the files inside the /static/assets folder
'/dataset/file.csv' # make public the file /static/dataset/file.csv
]
)
```

## User information in event handler

When the `user_info` route is configured, user information will be accessible
Expand Down
50 changes: 39 additions & 11 deletions src/writer/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os.path
import time
from abc import ABCMeta, abstractmethod
from typing import Callable, Dict, Optional
from typing import Callable, Dict, List, Optional
from urllib.parse import urlparse

from authlib.integrations.requests_client.oauth2_session import OAuth2Session # type: ignore
Expand Down Expand Up @@ -176,6 +176,7 @@ class Oidc(Auth):
scope: str = "openid email profile"
callback_authorize: str = "authorize"
url_userinfo: Optional[str] = None
public_static_assets: List[str] = dataclasses.field(default_factory=list)

authlib: OAuth2Session = None
callback_func: Optional[Callable[[Request, str, dict], None]] = None # Callback to validate user authentication
Expand All @@ -191,14 +192,24 @@ def register(self,
redirect_url = urljoin(self.host_url, self.callback_authorize)
host_url_path = urlpath(self.host_url)
callback_authorize_path = urljoin(host_url_path, self.callback_authorize)
static_assets_path = urljoin(host_url_path, "static")

auth_authorized_prefix_paths = [urljoin(host_url_path, "static"), urljoin(host_url_path, "extensions")]
auth_authorized_routes = [callback_authorize_path]

for p in self.public_static_assets:
name, ext = os.path.splitext(p)
if ext == '':
auth_authorized_prefix_paths.append(urljoin(host_url_path, p))
else:
auth_authorized_routes.append(urljoin(host_url_path, p))


logger.debug(f"[auth] oidc - url redirect: {redirect_url}")
logger.debug(f"[auth] oidc - endpoint authorize: {self.url_authorize}")
logger.debug(f"[auth] oidc - endpoint token: {self.url_oauthtoken}")
logger.debug(f"[auth] oidc - path: {host_url_path}")
logger.debug(f"[auth] oidc - authorize path: {callback_authorize_path}")
logger.debug(f"[auth] oidc - static asset path: {static_assets_path}")
logger.debug(f"[auth] oidc - auth authorized routes: {auth_authorized_routes}")
logger.debug(f"[auth] oidc - auth authorized prefix paths: {auth_authorized_prefix_paths}")
self.authlib = OAuth2Session(
client_id=self.client_id,
client_secret=self.client_secret,
Expand All @@ -215,7 +226,8 @@ def register(self,
async def oidc_middleware(request: Request, call_next):
session = request.cookies.get('session')

if session is not None or request.url.path in [callback_authorize_path] or request.url.path.startswith(static_assets_path):
is_one_of_url_prefix_allowed = any(request.url.path.startswith(url_prefix) for url_prefix in auth_authorized_prefix_paths)
if session is not None or request.url.path in [auth_authorized_routes] or is_one_of_url_prefix_allowed:
response: Response = await call_next(request)
return response
else:
Expand Down Expand Up @@ -259,7 +271,7 @@ async def route_callback(request: Request):
})


def Google(client_id: str, client_secret: str, host_url: str) -> Oidc:
def Google(client_id: str, client_secret: str, host_url: str, public_static_assets: Optional[List[str]]=None) -> Oidc:
"""
Configure Google Social login configured through Client Id for Web application in Google Cloud Console.
Expand All @@ -270,15 +282,20 @@ def Google(client_id: str, client_secret: str, host_url: str) -> Oidc:
:param client_secret: client secret of Web application
:param host_url: The URL of the Writer Framework application (for callback)
"""
if public_static_assets is None:
public_static_assets = []

return Oidc(
client_id=client_id,
client_secret=client_secret,
host_url=host_url,
url_authorize="https://accounts.google.com/o/oauth2/auth",
url_oauthtoken="https://oauth2.googleapis.com/token",
url_userinfo="https://www.googleapis.com/oauth2/v1/userinfo?alt=json")
url_userinfo="https://www.googleapis.com/oauth2/v1/userinfo?alt=json",
public_static_assets=public_static_assets
)

def Github(client_id: str, client_secret: str, host_url: str) -> Oidc:
def Github(client_id: str, client_secret: str, host_url: str, public_static_assets: Optional[List[str]]=None) -> Oidc:
"""
Configure Github authentication.
Expand All @@ -289,15 +306,20 @@ def Github(client_id: str, client_secret: str, host_url: str) -> Oidc:
:param client_secret: client secret
:param host_url: The URL of the Writer Framework application (for callback)
"""
if public_static_assets is None:
public_static_assets = []

return Oidc(
client_id=client_id,
client_secret=client_secret,
host_url=host_url,
url_authorize="https://github.com/login/oauth/authorize",
url_oauthtoken="https://github.com/login/oauth/access_token",
url_userinfo="https://api.github.com/user")
url_userinfo="https://api.github.com/user",
public_static_assets=public_static_assets
)

def Auth0(client_id: str, client_secret: str, domain: str, host_url: str) -> Oidc:
def Auth0(client_id: str, client_secret: str, domain: str, host_url: str, public_static_assets: Optional[List[str]]=None) -> Oidc:
"""
Configure Auth0 application for authentication.
Expand All @@ -308,14 +330,20 @@ def Auth0(client_id: str, client_secret: str, domain: str, host_url: str) -> Oid
:param client_secret: client secret
:param domain: Domain of the Auth0 application
:param host_url: The URL of the Writer Framework application (for callback)
:param public_static_assets: list of public static assets that are public
"""
if public_static_assets is None:
public_static_assets = []

return Oidc(
client_id=client_id,
client_secret=client_secret,
host_url=host_url,
url_authorize=f"https://{domain}/authorize",
url_oauthtoken=f"https://{domain}/oauth/token",
url_userinfo=f"https://{domain}/userinfo")
url_userinfo=f"https://{domain}/userinfo",
public_static_assets=public_static_assets
)

def urlpath(url: str):
"""
Expand Down

0 comments on commit 2d23b3c

Please sign in to comment.