-
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
970 additions
and
241 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,80 @@ | ||
# Security | ||
|
||
Security, authentication, and authorization can be approached in various ways. | ||
|
||
These topics are often considered complex and challenging to master. | ||
|
||
In many frameworks and systems, managing security and authentication requires significant effort, | ||
often accounting for 50% or more of the total codebase. | ||
|
||
**Esmerald**, however, offers a range of tools that simplify handling security. | ||
These tools enable you to implement secure systems quickly, efficiently, | ||
and in compliance with standards—without needing to dive deeply into all the technical specifications of security. | ||
|
||
Before diving in, let’s explore a few key concepts. | ||
|
||
## Quick Note | ||
|
||
If you don't need to worry about these concepts, terms and terminologies or you are simply familiar with those, you can | ||
jump directly to the next sections. | ||
|
||
## OAuth | ||
|
||
OAuth2 is a comprehensive specification that outlines multiple methods for managing authentication and authorization. | ||
|
||
It is designed to handle a wide range of complex scenarios. | ||
|
||
One of its key features is enabling authentication through a "third party." | ||
|
||
This is the foundation for systems that offer options like "Sign via Facebook", | ||
"Sign in using Google" or "Login via GitHub". | ||
|
||
### OAuth (first version) | ||
|
||
OAuth 1 was an earlier version of the OAuth specification, significantly different from OAuth2. | ||
It was more complex because it included detailed requirements for encrypting communication. | ||
|
||
Today, OAuth 1 is rarely used or popular. | ||
|
||
In contrast, OAuth2 simplifies this aspect by not defining how to encrypt communication. | ||
Instead, it assumes that your application is served over HTTPS, ensuring secure communication by | ||
relying on the encryption provided by the protocol. | ||
|
||
## OpenID Connect | ||
|
||
OpenID Connect is a specification built on top of **OAuth2**. | ||
|
||
It extends OAuth2 by addressing ambiguities in the original specification, aiming to improve interoperability across systems. | ||
|
||
For instance, Google login leverages OpenID Connect, which operates on top of OAuth2. | ||
|
||
However, Facebook login does not support OpenID Connect and instead uses its own customized version of OAuth2. | ||
|
||
## OpenID (not "OpenID Connect") | ||
|
||
There was also an earlier specification called "OpenID," which aimed to address the same challenges as **OpenID Connect**. | ||
However, it was not built on OAuth2 and functioned as a completely separate system. | ||
|
||
Unlike OpenID Connect, OpenID did not achieve widespread adoption and is rarely used today. | ||
|
||
## The OpenAPI | ||
|
||
Did you know that OpenAPI in the past was called **Swagger**? You probably did and this is we still have the *swagger ui* | ||
and the constant use of that name. | ||
|
||
**Esmerald** provides a native integration with **OpenAPI** as well with its automatic documentation generation. | ||
|
||
Why this? Well, using the OpenAPI specification it can also take advantage of the standard-based tools for security. | ||
|
||
The following `security schemes` are supported by OpenAPI: | ||
|
||
* `apiKey` - An application specific key that can come from: | ||
* Cookie parameter | ||
* Header parameter | ||
* Header parameter | ||
* `http` - The standard HTTP authentication system that includes: | ||
* `bearer` - An header `Authorization` followed by a value of `Bearer` with the corresponding token. | ||
* HTTP Basic Auth | ||
* HTTP Digest | ||
|
||
OpenAPI also supports the previously mentioned `OAuth2` and `OpenID Connect`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
# Interaction & Next Steps | ||
|
||
In the [previous chapter](./introduction.md), 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. | ||
|
||
```python hl_lines="9-10" | ||
{!> ../../../docs_src/security/app.py !} | ||
``` | ||
|
||
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](../encoders.md) | ||
making it versatile enough for your needs. | ||
|
||
For ths example, let us use the native Pydantic support. | ||
|
||
```python | ||
{!> ../../../docs_src/security/enhance.py !} | ||
``` | ||
|
||
## The `get_current_user` dependency | ||
|
||
Let's create a dependency called `get_current_user`. | ||
|
||
And remember, dependencies can have sub-dependencies, right? | ||
|
||
```python hl_lines="17" | ||
{!> ../../../docs_src/security/enhance.py !} | ||
``` | ||
|
||
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. | ||
|
||
```python hl_lines="13-14" | ||
{!> ../../../docs_src/security/enhance.py !} | ||
``` | ||
|
||
## 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](../dependencies.md). | ||
|
||
```python hl_lines="27" | ||
{!> ../../../docs_src/security/enhance.py !} | ||
``` | ||
|
||
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. | ||
|
||
```python hl_lines="27" | ||
{!> ../../../docs_src/security/enhance.py !} | ||
``` | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
# First Introduction | ||
|
||
Let's imagine that you have your backend API in some domain. | ||
|
||
And you have a frontend in another domain or in a different path of the same domain (or in a mobile application). | ||
|
||
And you want to have a way for the frontend to authenticate with the backend, using a username and password. | ||
|
||
We can use OAuth2 to build that with **Esmerald**. | ||
|
||
But let's save you the time of reading the full long specification just to find those little pieces of information you need. | ||
|
||
Let's use the tools provided by **Esmerald** to handle security. | ||
|
||
## Let us dig in | ||
|
||
We will be doing and explaining at the same time what is what. | ||
|
||
## Create an `app.py` | ||
|
||
You can copy the following code into an `app.py` or any file at your choice. | ||
|
||
```python | ||
{!> ../../../docs_src/security/app.py !} | ||
``` | ||
|
||
## Run it | ||
|
||
You can now run the file using, for example, `uvicorn` and it can be like this: | ||
|
||
```shell | ||
$ uvicorn app:app | ||
``` | ||
|
||
## Verify it | ||
|
||
To check if the endpoint is properly configured and working, you can access the OpenAPI documentation at | ||
[http://127.0.0.1:8000/docs/swagger](http://127.0.0.1:8000/docs/swagger). | ||
|
||
You should be able to see something like this: | ||
|
||
<img src="https://res.cloudinary.com/dymmond/image/upload/v1732010613/esmerald/security/txef6swlznhtjkshevfr.png"/> | ||
|
||
!!! Tip | ||
As you can see, you already have a brand new shiny **Authorize** button at the top of the page. | ||
The same is applied to the path operation that contains a lock icon as well. | ||
|
||
If you click the **Authorize** button, you will be able to see the type of login to type a `username`, `password` and | ||
other fields as well. | ||
|
||
Lets check and click it! | ||
|
||
<img src="https://res.cloudinary.com/dymmond/image/upload/v1732011171/esmerald/security/autorize_wqh6lu.png" /> | ||
|
||
!!! Note | ||
Typing anything in the form won't make it work, yet. Step by step we will get there, no worries. | ||
|
||
This isn't the frontend interface intended for end users. Instead, it serves as a powerful, interactive tool for documenting your API. | ||
|
||
It’s useful for the frontend team (which might also be you), for third-party applications and systems, and even for your own use. | ||
You can rely on it to debug, review, and test your application efficiently. | ||
|
||
## The `password` flow | ||
|
||
Now, let’s take a step back and clarify what this all means. | ||
|
||
The `password` "flow" is one of the methods (or "flows") defined in OAuth2 for managing security and authentication. | ||
|
||
OAuth2 was originally designed to separate the backend or API from the server responsible for user authentication. | ||
|
||
However, in this scenario, the same Esmerald application will handle both the API and the authentication process. | ||
|
||
Let’s examine it from this simplified perspective: | ||
|
||
Here’s how the password "flow" works step by step: | ||
|
||
1. **User Login**: The user enters their `username` and `password` in the frontend and submits the form by hitting `Enter`. | ||
|
||
2. **Frontend Request**: The frontend (running in the user’s browser) sends the `username` and `password` to a specific URL on the API, typically defined with `tokenUrl="token"`. | ||
|
||
3. **API Validation**: | ||
- The API verifies the provided `username` and `password`. | ||
- If valid, it responds with a "token." | ||
- A **token** is essentially a string containing information that can later be used to authenticate the user. | ||
- Tokens usually have an expiration time: | ||
- After expiration, the user must log in again. | ||
- This limits the risk if the token is stolen since it won’t work indefinitely (in most cases). | ||
|
||
4. **Token Storage**: The frontend temporarily stores the token securely. | ||
|
||
5. **Navigating the App**: When the user navigates to another section of the web app, the frontend may need to fetch additional data from the API. | ||
|
||
6. **Authenticated API Requests**: | ||
- To access protected endpoints, the frontend includes an `Authorization` header in its request. | ||
- The header’s value is `Bearer ` followed by the token. | ||
- For example, if the token is `foobar`, the `Authorization` header would look like this: | ||
|
||
```plaintext | ||
Authorization: Bearer foobar | ||
``` | ||
## **Esmerald** `OAuth2PasswordBearer` | ||
**Esmerald** offers various tools, at different levels of abstraction, to implement security features. | ||
In this example, we’ll use **OAuth2** with the **Password** flow, utilizing a **Bearer** token. To do this, we’ll use the `OAuth2PasswordBearer` class. | ||
!!! info | ||
A "bearer" token isn’t the only option for authentication. However, it’s the most suitable for our use case and often the best choice for most scenarios. | ||
Unless you’re an OAuth2 expert and know of another option that better fits your needs, **Esmerald** gives you the flexibility to implement other options as well. | ||
When creating an instance of the `OAuth2PasswordBearer` class, we provide the `tokenUrl` parameter. This specifies the URL that the frontend (running in the user's browser) will use to send the `username` and `password` in order to obtain the token. | ||
When we create an instance of the `OAuth2PasswordBearer` class, we provide the `tokenUrl` parameter. This URL is where the client (the frontend running in the user's browser) will send the `username` and `password` in order to obtain a token. | ||
```python hl_lines="6" | ||
{!> ../../../docs_src/security/app.py !} | ||
``` | ||
|
||
!!! Tip | ||
Here, `tokenUrl="token"` refers to a relative URL, `token`, which we haven’t created yet. Since it’s a relative URL, it’s equivalent to `./token`. | ||
|
||
This means that if your API is hosted at `https://example.com/`, the full URL would be `https://example.com/token`. If your API is at `https://example.com/api/v1/`, then the full URL would be `https://example.com/api/v1/token`. | ||
|
||
Using a relative URL is important, as it ensures your application continues to function correctly, even in more advanced scenarios, like when running **Behind a Proxy**. | ||
|
||
This parameter doesn’t automatically create the `/token` endpoint or path operation. Instead, it simply declares that the URL `/token` will be the endpoint that the client should use to obtain the token. | ||
|
||
This information is then used in OpenAPI and displayed in the interactive API documentation, guiding the client on where to send the request for the token. | ||
|
||
We will create the actual path operation for this endpoint shortly. | ||
|
||
The `oauth2_scheme` variable is an instance of the `OAuth2PasswordBearer` class, but it is also a "callable" object. | ||
|
||
This means that you can use it as a function, like this: | ||
|
||
```Python | ||
oauth2_scheme(some, parameters) | ||
``` | ||
|
||
When called, it will handle the extraction of the token from the request, typically from the `Authorization` header. | ||
|
||
So, it can be used with `Inject()` and `Injects()`. | ||
|
||
### Use it | ||
|
||
Now you can pass that `oauth2_scheme` in a dependency with `Inject` and `Injects` natively from Esmerald. | ||
|
||
```python hl_lines="9-10" | ||
{!> ../../../docs_src/security/app.py !} | ||
``` | ||
|
||
The `security` in the handler is what allows the OpenAPI specification to understand what needs to go in the **Authorize**. | ||
|
||
This dependency will provide a `str` that gets assigned to the `token` parameter of the *path operation function*. | ||
|
||
**Esmerald** will automatically recognize this dependency and use it to define a "security scheme" in the OpenAPI schema. This also makes the security scheme visible in the automatic API documentation, helping both developers and users understand how authentication works for the API. | ||
|
||
!!! info | ||
**Esmerald** knows it can use the `OAuth2PasswordBearer` class (declared as a dependency) to define the security scheme in OpenAPI because `OAuth2PasswordBearer` inherits from `esmerald.security.oauth2.OAuth2`, which, in turn, inherits from `esmerald.security.base.SecurityBase`. | ||
|
||
All security utilities that integrate with OpenAPI and the automatic API documentation inherit from `SecurityBase`. This inheritance structure allows **Esmerald** to automatically recognize and integrate these security features into the OpenAPI schema, ensuring they are properly displayed in the API docs. | ||
|
||
## What does it do | ||
|
||
**Esmerald** will automatically look for the `Authorization` header in the request, check if it contains a value starting with `Bearer ` followed by a token, and return that token as a `str`. | ||
|
||
If it doesn't find an `Authorization` header or if the value doesn't contain a valid `Bearer` token, **Esmerald** will immediately respond with a `401 Unauthorized` error. | ||
|
||
You don't need to manually check for the token or handle the error yourself, **Esmerald** ensures that if your function is executed, the `token` parameter will always contain a valid `str`. | ||
|
||
You can even test this behavior in the interactive documentation to see how it works in action. | ||
|
||
<img src="https://res.cloudinary.com/dymmond/image/upload/v1732014010/esmerald/security/try_g20hqn.png" /> | ||
|
||
That's correct! At this stage, we're not verifying the validity of the token yet. We're simply extracting it from the `Authorization` header and passing it as a string to the path operation function. | ||
|
||
This is an important first step, as it lays the groundwork for authentication. Later, you can implement the logic to validate the token (e.g., checking its signature, expiration, etc.). But for now, this setup ensures that the token is correctly extracted and available for further use. |
Oops, something went wrong.