Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions docs_src/src/pages/documentation/api_reference/views.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,143 @@ The only thing to remember is that you need to add the subrouter to the main rou
</Col>
</Row>

### Authentication on Subrouters

Batman asked Robyn how to secure his routes. Robyn explained that authentication can be added to subrouters in three ways, giving developers flexibility to decide the scope and precedence of the authentication.

> **Important**: If any of these methods are used, the authentication for the endpoint is set to `False` unless explicitly overridden.

---
<Row>
<Col>
#### 1. Authentication at the Endpoint Level

Authentication can be applied directly to a specific endpoint using the decorator for that endpoint. This method has the **highest precedence**, meaning it overrides any authentication settings defined at the router or subrouter level.
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/secured , /unsecured">
```python {{ title: 'untyped' }}
from robyn import Robyn, SubRouter

app = Robyn(__file__)

sub_router = SubRouter("/auth_example")

@sub_router.get("/secured", auth_required=True)
def secured(request):
return "This endpoint is secured with authentication!"

@sub_router.get("/unsecured", auth_required=False)
def unsecured(request):
return "This endpoint does not require authentication!"

app.include_router(sub_router)
```

```python {{ title: 'typed' }}
from robyn import Robyn, SubRouter, Request

app = Robyn(__file__)

sub_router = SubRouter("/auth_example")

@sub_router.get("/secured", auth_required=True)
def secured(request : Request) -> str:
return "This endpoint is secured with authentication!"

@sub_router.get("/unsecured", auth_required=False)
def unsecured(request : Request) -> str:
return "This endpoint does not require authentication!"

app.include_router(sub_router)
```
</CodeGroup>
</Col>
</Row>


<Row>
<Col>
#### 2. Authentication via `include_router`
When including a subrouter into the main router, you can specify an authentication flag. This setting applies to all endpoints in the subrouter unless overridden at the endpoint level.
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/default_secured">
```python {{ title: 'untyped' }}
from robyn import Robyn, SubRouter

app = Robyn(__file__)

sub_router = SubRouter("/auth_example")

@sub_router.get("/default_secured")
def default_secured(request):
return "Authentication is set by include_router!"

app.include_router(sub_router, auth_required=True)
```

```python {{ title: 'typed' }}
from robyn import Robyn, SubRouter, Request

app = Robyn(__file__)

sub_router = SubRouter("/auth_example")

@sub_router.get("/default_secured")
def default_secured(request : Request) -> str:
return "Authentication is set by include_router!"

app.include_router(sub_router, auth_required=True)
```
</CodeGroup>
</Col>
</Row>

<Row>
<Col>
#### 3. Authentication at SubRouter Instantiation
Finally, you can define authentication at the time of the `SubRouter` instantiation. This acts as the **default authentication** setting for all endpoints in the subrouter unless overridden by the endpoint decorator or during inclusion into the main router.
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/inherited_auth">
```python {{ title: 'untyped' }}
from robyn import Robyn, SubRouter

app = Robyn(__file__)

sub_router = SubRouter("/auth_example", auth_required=True)

@sub_router.get("/inherited_auth")
def inherited_auth(request):
return "Authentication is inherited from the SubRouter!"

app.include_router(sub_router)
```

```python {{ title: 'typed' }}
from robyn import Robyn, SubRouter, Request

app = Robyn(__file__)

sub_router = SubRouter("/auth_example", auth_required=True)

@sub_router.get("/inherited_auth")
def inherited_auth(request : Request) -> str:
return "Authentication is inherited from the SubRouter!"

app.include_router(sub_router)
```
</CodeGroup>
</Col>
</Row>
#### Precedence Rules
Authentication precedence in Robyn follows this order:

1. Endpoint-level decorator: If an endpoint explicitly sets `auth_required=True` or `auth_required=False`, this takes priority.
2. `include_router` method: The auth_required parameter in app.include_router applies to all endpoints in the included subrouter unless overridden.
3. `SubRouter` instantiation: The auth_required setting during SubRouter creation acts as the default for all its endpoints unless overridden at the endpoint or include_router level.

## What's next?

Now, that Batman knows how to organise his code, he wants to know how to add dependencies to his code. Robyn tells him that he can add dependencies at the global level, router level and the view level.
Expand Down
20 changes: 17 additions & 3 deletions integration_tests/base_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
from collections import defaultdict
from typing import Optional

from integration_tests.subroutes import di_subrouter, sub_router
from integration_tests.subroutes import (
di_subrouter,
sub_router,
auth_subrouter_endpoint,
auth_subrouter_include,
auth_subrouter_instance,
auth_subrouter_include_false,
auth_subrouter_include_true,
)
from integration_tests.views import AsyncView, SyncView
from robyn import Headers, Request, Response, Robyn, WebSocket, WebSocketConnector, jsonify, serve_file, serve_html
from robyn.authentication import AuthenticationHandler, BearerGetter, Identity
Expand Down Expand Up @@ -1090,8 +1098,6 @@ def main():
app.startup_handler(startup_handler)
app.add_view("/sync/view", SyncView)
app.add_view("/async/view", AsyncView)
app.include_router(sub_router)
app.include_router(di_subrouter)

class BasicAuthHandler(AuthenticationHandler):
def authenticate(self, request: Request) -> Optional[Identity]:
Expand All @@ -1103,7 +1109,15 @@ def authenticate(self, request: Request) -> Optional[Identity]:
return Identity(claims={"key": "value"})
return None

app.include_router(sub_router)
app.include_router(di_subrouter)
app.include_router(auth_subrouter_endpoint)
app.include_router(auth_subrouter_include, auth_required=True)
app.include_router(auth_subrouter_instance)
app.include_router(auth_subrouter_include_false, auth_required=False)
app.include_router(auth_subrouter_include_true, auth_required=True)
app.configure_authentication(BasicAuthHandler(token_getter=BearerGetter()))

app.start(port=8080, _check_port=False)


Expand Down
18 changes: 17 additions & 1 deletion integration_tests/subroutes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
from robyn import SubRouter, WebSocket, jsonify

from .di_subrouter import di_subrouter
from .auth_subroutes import (
auth_subrouter_endpoint,
auth_subrouter_include,
auth_subrouter_instance,
auth_subrouter_include_false,
auth_subrouter_include_true,
)

sub_router = SubRouter(__name__, prefix="/sub_router")

websocket = WebSocket(sub_router, "/ws")

__all__ = ["sub_router", "websocket", "di_subrouter"]
__all__ = [
"sub_router",
"websocket",
"di_subrouter",
"auth_subrouter_endpoint",
"auth_subrouter_include",
"auth_subrouter_instance",
"auth_subrouter_include_false",
"auth_subrouter_include_true",
]


@websocket.on("connect")
Expand Down
125 changes: 125 additions & 0 deletions integration_tests/subroutes/auth_subroutes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
from robyn import Request, SubRouter

auth_subrouter_endpoint = SubRouter(__file__, "/auth_subrouter_endpoint")


@auth_subrouter_endpoint.get("/auth_subroute_sync", auth_required=True)
def sync_subrouter_auth_endpoint(request: Request):
assert request.identity is not None
assert request.identity.claims == {"key": "value"}
return "authenticated"


@auth_subrouter_endpoint.get("/auth_subroute_async", auth_required=True)
async def async_subrouter_auth_endpoint(request: Request):
assert request.identity is not None
assert request.identity.claims == {"key": "value"}
return "authenticated"


auth_subrouter_include = SubRouter(__file__, "/auth_subrouter_include")


@auth_subrouter_include.get("/auth_subroute_sync")
def sync_subrouter_auth_include(request: Request):
assert request.identity is not None
assert request.identity.claims == {"key": "value"}
return "authenticated"


@auth_subrouter_include.get("/auth_subroute_async")
async def async_subrouter_auth_include(request: Request):
assert request.identity is not None
assert request.identity.claims == {"key": "value"}
return "authenticated"


@auth_subrouter_include.get("/noauth_subroute_sync", auth_required=False)
def sync_subrouter_noauth_include(request: Request):
return "bypassed"


@auth_subrouter_include.get("/noauth_subroute_async", auth_required=False)
async def async_subrouter_noauth_include(request: Request):
return "bypassed"


auth_subrouter_instance = SubRouter(__file__, "/auth_subrouter_instance", auth_required=True)


@auth_subrouter_instance.get("/auth_subroute_sync")
def sync_subrouter_auth_instance(request: Request):
assert request.identity is not None
assert request.identity.claims == {"key": "value"}
return "authenticated"


@auth_subrouter_instance.get("/auth_subroute_async")
async def async_subrouter_auth_instance(request: Request):
assert request.identity is not None
assert request.identity.claims == {"key": "value"}
return "authenticated"


@auth_subrouter_instance.get("/noauth_subroute_sync", auth_required=False)
def sync_subrouter_noauth_instance(request: Request):
return "bypassed"


@auth_subrouter_instance.get("/noauth_subroute_async", auth_required=False)
async def async_subrouter_noauth_instance(request: Request):
return "bypassed"


auth_subrouter_include_false = SubRouter(__file__, "/auth_subrouter_include_false", auth_required=True)


@auth_subrouter_include_false.get("/auth_subroute_sync", auth_required=True)
def sync_subrouter_auth_include_false(request: Request):
assert request.identity is not None
assert request.identity.claims == {"key": "value"}
return "authenticated"


@auth_subrouter_include_false.get("/auth_subroute_async", auth_required=True)
async def async_subrouter_auth_include_false(request: Request):
assert request.identity is not None
assert request.identity.claims == {"key": "value"}
return "authenticated"


@auth_subrouter_include_false.get("/noauth_subroute_sync")
def sync_subrouter_noauth_include_false(request: Request):
return "bypassed"


@auth_subrouter_include_false.get("/noauth_subroute_async")
async def async_subrouter_noauth_include_false(request: Request):
return "bypassed"


auth_subrouter_include_true = SubRouter(__file__, "/auth_subrouter_include_true", auth_required=False)


@auth_subrouter_include_true.get("/auth_subroute_sync")
def sync_subrouter_auth_include_true(request: Request):
assert request.identity is not None
assert request.identity.claims == {"key": "value"}
return "authenticated"


@auth_subrouter_include_true.get("/auth_subroute_async")
async def async_subrouter_auth_include_true(request: Request):
assert request.identity is not None
assert request.identity.claims == {"key": "value"}
return "authenticated"


@auth_subrouter_include_true.get("/noauth_subroute_sync", auth_required=False)
def sync_subrouter_noauth_include_true(request: Request):
return "bypassed"


@auth_subrouter_include_true.get("/noauth_subroute_async", auth_required=False)
async def async_subrouter_noauth_include_true(request: Request):
return "bypassed"
Loading