Interaction & Next Steps¶
In the previous chapter, the security system—based on Esmerald's dependency injection system was providing the path operation function
with a token
as a str
.
This token was extracted from the Authorization
header of the incoming request. The security system automatically handled this, so the function didn't need to worry about how the token was retrieved. The function simply received the token as a string, which it could then use for further processing, such as verifying the token's validity or checking user permissions.
from typing import Any, Dict
from esmerald import Inject, Injects, Esmerald, get, Gateway
from esmerald.security.oauth2 import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@get("/items", dependencies={"token": Inject(oauth2_scheme)}, security=[oauth2_scheme])
async def get_items(token: str = Injects()) -> Dict[str, Any]:
return {"token": token}
app = Esmerald(
routes=[
Gateway(handler=get_items),
]
)
That’s still not very useful as it is.
Let’s enhance it by returning the current user instead.
Create a user model¶
By creating a user
model you can use Pydantic
, msgspec or whatever you want since Esmerald supports the encoders
making it versatile enough for your needs.
For ths example, let us use the native Pydantic support.
from esmerald import Inject, Injects, get, Security
from esmerald.security.oauth2 import OAuth2PasswordBearer
from pydantic import BaseModel
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str | None = None
def fake_decode_token(token):
return User(username=token + "fakedecoded", email="john@example.com")
async def get_current_user(token: str = Security(oauth2_scheme)):
user = fake_decode_token(token)
return user
@get(
"/users/me",
dependencies={"current_user": Inject(get_current_user)},
security=[oauth2_scheme],
)
async def users_me(current_user: User = Injects()) -> User:
return current_user
The get_current_user
dependency¶
Let's create a dependency called get_current_user
.
And remember, dependencies can have sub-dependencies, right?
from esmerald import Inject, Injects, get, Security
from esmerald.security.oauth2 import OAuth2PasswordBearer
from pydantic import BaseModel
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str | None = None
def fake_decode_token(token):
return User(username=token + "fakedecoded", email="john@example.com")
async def get_current_user(token: str = Security(oauth2_scheme)):
user = fake_decode_token(token)
return user
@get(
"/users/me",
dependencies={"current_user": Inject(get_current_user)},
security=[oauth2_scheme],
)
async def users_me(current_user: User = Injects()) -> User:
return current_user
The get_current_user
dependency will depend on the same oauth2_scheme
we created earlier.
Just like we did before in the path operation itself, our new get_current_user
dependency will receive a token
as a str
from the oauth2_scheme
sub-dependency.
Warning
You can see a Security
object there in the sub-dependency, right? Well, yes, that Security
object that depends
of the scheme
can only be called using this object.
In other words, when a sub-dependency is a oauth2_scheme
type of thing or any security related, you must use the Security
object.
This special object once its declared, Esmerald will know what to do with it and make sure it can be executed properly.
Esmerald dependency system is extremely powerful and extremely versatile and therefore some special objects dedicated to this security approach were added to make our lives simples.
Get the user¶
The get_current_user
dependency will use a (fake) utility function we created. This function takes the token as a str
and returns our Pydantic User
model.
from esmerald import Inject, Injects, get, Security
from esmerald.security.oauth2 import OAuth2PasswordBearer
from pydantic import BaseModel
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str | None = None
def fake_decode_token(token):
return User(username=token + "fakedecoded", email="john@example.com")
async def get_current_user(token: str = Security(oauth2_scheme)):
user = fake_decode_token(token)
return user
@get(
"/users/me",
dependencies={"current_user": Inject(get_current_user)},
security=[oauth2_scheme],
)
async def users_me(current_user: User = Injects()) -> User:
return current_user
Inject the current user¶
Now, we can use the Inject
and Injects
with our get_current_user
dependency in the path operation. This is part
of the special Esmerlad dependency inject system that is also multi layered. You can read again about the
dependency injection with Esmerald.
from esmerald import Inject, Injects, get, Security
from esmerald.security.oauth2 import OAuth2PasswordBearer
from pydantic import BaseModel
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str | None = None
def fake_decode_token(token):
return User(username=token + "fakedecoded", email="john@example.com")
async def get_current_user(token: str = Security(oauth2_scheme)):
user = fake_decode_token(token)
return user
@get(
"/users/me",
dependencies={"current_user": Inject(get_current_user)},
security=[oauth2_scheme],
)
async def users_me(current_user: User = Injects()) -> User:
return current_user
Notice that we declare the type of current_user
as the Pydantic model User
.
This ensures that we get type checking and auto-completion support inside the function, making development smoother and more error-free.
Now, you can directly access the current user in the path operation functions and handle the security mechanisms at the Dependency Injection level, using Depends
.
You can use any model or data for your security requirements (in this case, a Pydantic model User
), but you're not limited to a specific data model, class, or type.
For example:
- Want to use an id
and email
instead of a username
in your model? No problem, just use the same tools.
- Prefer a str
or a dict
? Or perhaps a database class model instance directly? It all works seamlessly.
- If you have bots, robots, or other systems logging in instead of users, and they only need an access token, that's fine too.
You can use any model, class, or database structure that fits your application's needs. Esmerald's dependency injection system makes it easy and flexible for all cases.
Code size so far¶
This example might seem a bit verbose, but remember, we're combining security, data models, utility functions, and path operations in the same file.
Here’s the key takeaway:
The security and dependency injection setup is written once.
You can make it as complex as you need, but it only needs to be defined in one place. The beauty of Esmerald is its flexibility—whether simple or complex, you only write this logic once.
And once it's set up, you can reuse it across thousands of endpoints (path operations).
All of these endpoints (or any portion of them) can take advantage of the same dependencies or any others you create.
Even with thousands of path operations, many of them can be as simple as just a few lines of code.
from esmerald import Inject, Injects, get, Security
from esmerald.security.oauth2 import OAuth2PasswordBearer
from pydantic import BaseModel
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str | None = None
def fake_decode_token(token):
return User(username=token + "fakedecoded", email="john@example.com")
async def get_current_user(token: str = Security(oauth2_scheme)):
user = fake_decode_token(token)
return user
@get(
"/users/me",
dependencies={"current_user": Inject(get_current_user)},
security=[oauth2_scheme],
)
async def users_me(current_user: User = Injects()) -> User:
return current_user
Remember that Esmerald has a flexible dependency injection system and the lines can be cut by a lot avoiding repetition.
You can now access the current user directly in your path operation function.
We're already halfway there.
Next, we just need to add a path operation that allows the user/client to send their username
and password
to get the token. That will be our next step.