Security Backends#
AbstractSecurityConfig#
starlite.security includes an AbstractSecurityConfig
class
that serves as a basis for all the security backends offered by Starlite, and is also meant to be used as a basis for
custom security backends created by users.
Session Auth Backend#
Starlite offers a builtin session auth backend that can be used out of the box with any of the session backends supported by the Starlite session middleware.
Using Session Auth#
from typing import Any, Dict, Literal, Optional
from uuid import UUID, uuid4
from pydantic import BaseModel, EmailStr, SecretStr
from starlite import (
ASGIConnection,
NotAuthorizedException,
OpenAPIConfig,
Request,
Starlite,
get,
post,
)
from starlite.middleware.session.memory_backend import MemoryBackendConfig
from starlite.security.session_auth import SessionAuth
# Let's assume we have a User model that is a pydantic model.
# This though is not required - we need some sort of user class -
# but it can be any arbitrary value, e.g. an SQLAlchemy model,
# a representation of a MongoDB etc.
class User(BaseModel):
id: UUID
name: str
email: EmailStr
# we also have pydantic types for two different
# kinds of POST request bodies: one for creating
# a user, e.g. "sign-up", and the other for logging
# an existing user in.
class UserCreatePayload(BaseModel):
name: str
email: EmailStr
password: SecretStr
class UserLoginPayload(BaseModel):
email: EmailStr
password: SecretStr
# The SessionAuth class requires a handler callable
# that takes the session dictionary, and returns the
# 'User' instance correlating to it.
#
# The session dictionary itself is a value the user decides
# upon. So for example, it might be a simple dictionary
# that holds a user id, for example: { "id": "abcd123" }
#
# Note: The callable can be either sync or async - both will work.
async def retrieve_user_handler(session: Dict[str, Any], connection: ASGIConnection) -> Optional[User]:
# we retrieve the user instance based on session data
value = await connection.cache.get(session.get("user_id", ""))
if value:
return User(**value)
return None
@post("/login")
async def login(data: UserLoginPayload, request: Request) -> User:
# we received log-in data via post.
# our login handler should retrieve from persistence (a db etc.)
# the user data and verify that the login details
# are correct. If we are using passwords, we should check that
# the password hashes match etc. We will simply assume that we
# have done all of that we now have a user value:
user_id = await request.cache.get(data.email)
if not user_id:
raise NotAuthorizedException
user_data = await request.cache.get(user_id)
# once verified we can create a session.
# to do this we simply need to call the Starlite
# 'Request.set_session' method, which accepts either dictionaries
# or pydantic models. In our case, we can simply record a
# simple dictionary with the user ID value:
request.set_session({"user_id": user_id})
# you can do whatever we want here. In this case, we will simply return the user data:
return User(**user_data)
@post("/signup")
async def signup(data: UserCreatePayload, request: Request) -> User:
# this is similar to the login handler, except here we should
# insert into persistence - after doing whatever extra
# validation we might require. We will assume that this is done,
# and we now have a user instance with an assigned ID value:
user = User(name=data.name, email=data.email, id=uuid4())
await request.cache.set(data.email, str(user.id))
await request.cache.set(str(user.id), user.dict())
# we are creating a session the same as we do in the
# 'login_handler' above:
request.set_session({"user_id": str(user.id)})
# and again, you can add whatever logic is required here, we
# will simply return the user:
return user
# the endpoint below requires the user to be already authenticated
# to be able to access it.
@get("/user")
def get_user(request: Request[User, Dict[Literal["user_id"], str]]) -> Any:
# because this route requires authentication, we can access
# `request.user`, which is the authenticated user returned
# by the 'retrieve_user_handler' function we passed to SessionAuth.
return request.user
# We add the session security schema to the OpenAPI config.
openapi_config = OpenAPIConfig(
title="My API",
version="1.0.0",
)
session_auth = SessionAuth[User](
retrieve_user_handler=retrieve_user_handler,
# we must pass a config for a session backend.
# all session backends are supported
session_backend_config=MemoryBackendConfig(),
# exclude any URLs that should not have authentication.
# We exclude the documentation URLs, signup and login.
exclude=["/login", "/signup", "/schema"],
)
# We initialize the app instance, passing to it the 'session_auth.on_app_init' and the 'openapi_config'.
app = Starlite(
route_handlers=[login, signup, get_user],
on_app_init=[session_auth.on_app_init],
openapi_config=openapi_config,
)
Using Session Auth#
from typing import Any, Literal, Optional
from uuid import UUID, uuid4
from pydantic import BaseModel, EmailStr, SecretStr
from starlite import (
ASGIConnection,
NotAuthorizedException,
OpenAPIConfig,
Request,
Starlite,
get,
post,
)
from starlite.middleware.session.memory_backend import MemoryBackendConfig
from starlite.security.session_auth import SessionAuth
# Let's assume we have a User model that is a pydantic model.
# This though is not required - we need some sort of user class -
# but it can be any arbitrary value, e.g. an SQLAlchemy model,
# a representation of a MongoDB etc.
class User(BaseModel):
id: UUID
name: str
email: EmailStr
# we also have pydantic types for two different
# kinds of POST request bodies: one for creating
# a user, e.g. "sign-up", and the other for logging
# an existing user in.
class UserCreatePayload(BaseModel):
name: str
email: EmailStr
password: SecretStr
class UserLoginPayload(BaseModel):
email: EmailStr
password: SecretStr
# The SessionAuth class requires a handler callable
# that takes the session dictionary, and returns the
# 'User' instance correlating to it.
#
# The session dictionary itself is a value the user decides
# upon. So for example, it might be a simple dictionary
# that holds a user id, for example: { "id": "abcd123" }
#
# Note: The callable can be either sync or async - both will work.
async def retrieve_user_handler(session: dict[str, Any], connection: ASGIConnection) -> Optional[User]:
# we retrieve the user instance based on session data
value = await connection.cache.get(session.get("user_id", ""))
if value:
return User(**value)
return None
@post("/login")
async def login(data: UserLoginPayload, request: Request) -> User:
# we received log-in data via post.
# our login handler should retrieve from persistence (a db etc.)
# the user data and verify that the login details
# are correct. If we are using passwords, we should check that
# the password hashes match etc. We will simply assume that we
# have done all of that we now have a user value:
user_id = await request.cache.get(data.email)
if not user_id:
raise NotAuthorizedException
user_data = await request.cache.get(user_id)
# once verified we can create a session.
# to do this we simply need to call the Starlite
# 'Request.set_session' method, which accepts either dictionaries
# or pydantic models. In our case, we can simply record a
# simple dictionary with the user ID value:
request.set_session({"user_id": user_id})
# you can do whatever we want here. In this case, we will simply return the user data:
return User(**user_data)
@post("/signup")
async def signup(data: UserCreatePayload, request: Request) -> User:
# this is similar to the login handler, except here we should
# insert into persistence - after doing whatever extra
# validation we might require. We will assume that this is done,
# and we now have a user instance with an assigned ID value:
user = User(name=data.name, email=data.email, id=uuid4())
await request.cache.set(data.email, str(user.id))
await request.cache.set(str(user.id), user.dict())
# we are creating a session the same as we do in the
# 'login_handler' above:
request.set_session({"user_id": str(user.id)})
# and again, you can add whatever logic is required here, we
# will simply return the user:
return user
# the endpoint below requires the user to be already authenticated
# to be able to access it.
@get("/user")
def get_user(request: Request[User, dict[Literal["user_id"], str]]) -> Any:
# because this route requires authentication, we can access
# `request.user`, which is the authenticated user returned
# by the 'retrieve_user_handler' function we passed to SessionAuth.
return request.user
# We add the session security schema to the OpenAPI config.
openapi_config = OpenAPIConfig(
title="My API",
version="1.0.0",
)
session_auth = SessionAuth[User](
retrieve_user_handler=retrieve_user_handler,
# we must pass a config for a session backend.
# all session backends are supported
session_backend_config=MemoryBackendConfig(),
# exclude any URLs that should not have authentication.
# We exclude the documentation URLs, signup and login.
exclude=["/login", "/signup", "/schema"],
)
# We initialize the app instance, passing to it the 'session_auth.on_app_init' and the 'openapi_config'.
app = Starlite(
route_handlers=[login, signup, get_user],
on_app_init=[session_auth.on_app_init],
openapi_config=openapi_config,
)
Using Session Auth#
from typing import Any, Literal
from uuid import UUID, uuid4
from pydantic import BaseModel, EmailStr, SecretStr
from starlite import (
ASGIConnection,
NotAuthorizedException,
OpenAPIConfig,
Request,
Starlite,
get,
post,
)
from starlite.middleware.session.memory_backend import MemoryBackendConfig
from starlite.security.session_auth import SessionAuth
# Let's assume we have a User model that is a pydantic model.
# This though is not required - we need some sort of user class -
# but it can be any arbitrary value, e.g. an SQLAlchemy model,
# a representation of a MongoDB etc.
class User(BaseModel):
id: UUID
name: str
email: EmailStr
# we also have pydantic types for two different
# kinds of POST request bodies: one for creating
# a user, e.g. "sign-up", and the other for logging
# an existing user in.
class UserCreatePayload(BaseModel):
name: str
email: EmailStr
password: SecretStr
class UserLoginPayload(BaseModel):
email: EmailStr
password: SecretStr
# The SessionAuth class requires a handler callable
# that takes the session dictionary, and returns the
# 'User' instance correlating to it.
#
# The session dictionary itself is a value the user decides
# upon. So for example, it might be a simple dictionary
# that holds a user id, for example: { "id": "abcd123" }
#
# Note: The callable can be either sync or async - both will work.
async def retrieve_user_handler(session: dict[str, Any], connection: ASGIConnection) -> User | None:
# we retrieve the user instance based on session data
value = await connection.cache.get(session.get("user_id", ""))
if value:
return User(**value)
return None
@post("/login")
async def login(data: UserLoginPayload, request: Request) -> User:
# we received log-in data via post.
# our login handler should retrieve from persistence (a db etc.)
# the user data and verify that the login details
# are correct. If we are using passwords, we should check that
# the password hashes match etc. We will simply assume that we
# have done all of that we now have a user value:
user_id = await request.cache.get(data.email)
if not user_id:
raise NotAuthorizedException
user_data = await request.cache.get(user_id)
# once verified we can create a session.
# to do this we simply need to call the Starlite
# 'Request.set_session' method, which accepts either dictionaries
# or pydantic models. In our case, we can simply record a
# simple dictionary with the user ID value:
request.set_session({"user_id": user_id})
# you can do whatever we want here. In this case, we will simply return the user data:
return User(**user_data)
@post("/signup")
async def signup(data: UserCreatePayload, request: Request) -> User:
# this is similar to the login handler, except here we should
# insert into persistence - after doing whatever extra
# validation we might require. We will assume that this is done,
# and we now have a user instance with an assigned ID value:
user = User(name=data.name, email=data.email, id=uuid4())
await request.cache.set(data.email, str(user.id))
await request.cache.set(str(user.id), user.dict())
# we are creating a session the same as we do in the
# 'login_handler' above:
request.set_session({"user_id": str(user.id)})
# and again, you can add whatever logic is required here, we
# will simply return the user:
return user
# the endpoint below requires the user to be already authenticated
# to be able to access it.
@get("/user")
def get_user(request: Request[User, dict[Literal["user_id"], str]]) -> Any:
# because this route requires authentication, we can access
# `request.user`, which is the authenticated user returned
# by the 'retrieve_user_handler' function we passed to SessionAuth.
return request.user
# We add the session security schema to the OpenAPI config.
openapi_config = OpenAPIConfig(
title="My API",
version="1.0.0",
)
session_auth = SessionAuth[User](
retrieve_user_handler=retrieve_user_handler,
# we must pass a config for a session backend.
# all session backends are supported
session_backend_config=MemoryBackendConfig(),
# exclude any URLs that should not have authentication.
# We exclude the documentation URLs, signup and login.
exclude=["/login", "/signup", "/schema"],
)
# We initialize the app instance, passing to it the 'session_auth.on_app_init' and the 'openapi_config'.
app = Starlite(
route_handlers=[login, signup, get_user],
on_app_init=[session_auth.on_app_init],
openapi_config=openapi_config,
)
JWT Auth#
Starlite also includes several JWT security backends under the contrib package, checkout the jwt contrib documentation for more details.