Skip to content

Commit

Permalink
Docs update
Browse files Browse the repository at this point in the history
  • Loading branch information
tarsil committed Dec 9, 2024
1 parent dc85ef9 commit ef8b75d
Show file tree
Hide file tree
Showing 14 changed files with 970 additions and 241 deletions.
1 change: 0 additions & 1 deletion docs/en/docs/security/first-steps.md

This file was deleted.

79 changes: 79 additions & 0 deletions docs/en/docs/security/index.md
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`.
111 changes: 111 additions & 0 deletions docs/en/docs/security/interaction.md
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.
180 changes: 180 additions & 0 deletions docs/en/docs/security/introduction.md
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.
Loading

0 comments on commit ef8b75d

Please sign in to comment.