Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Various changes and updates #433

Merged
merged 28 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ca30ee7
Fix shell loading default
tarsil Nov 14, 2024
cf3c87d
Fix linting
tarsil Nov 14, 2024
daa8983
Add O2Auth for OpenAPI and security in general
tarsil Nov 15, 2024
216d783
Add new tests for OAuth and add Security
tarsil Nov 15, 2024
81b9add
Add extra test
tarsil Nov 15, 2024
5ba0433
Implement security dependency for OAuth and OpenId
tarsil Nov 17, 2024
7b9b7ba
Update param_functions.py
tarsil Nov 17, 2024
d245b94
Change signature.py
tarsil Nov 17, 2024
1dc3cd5
Update test_security_oauth2_optional_desc.py
tarsil Nov 17, 2024
884de68
Merge branch 'main' into feat/oauth
tarsil Nov 17, 2024
e94ac3b
Fix optional data payload encoding validation
tarsil Nov 18, 2024
2af226b
Add extra test for union with different payload data
tarsil Nov 18, 2024
9213a2d
Add new API tests
tarsil Nov 18, 2024
bc42466
Update internal ignore security params in the query
tarsil Nov 18, 2024
fcc3366
Add tests for basic
tarsil Nov 18, 2024
a44fec2
Add digest tests
tarsil Nov 18, 2024
2016d5e
Add new security docs section
tarsil Nov 18, 2024
a62ed10
Update internal docs
tarsil Nov 18, 2024
dc85ef9
Add extra openid test
tarsil Nov 19, 2024
c3be28b
Merge branch 'main' into feat/oauth
tarsil Nov 29, 2024
ef8b75d
Docs update
tarsil Dec 9, 2024
2b3d043
Merge branch 'feat/oauth' of github.com:dymmond/esmerald into feat/oauth
tarsil Dec 9, 2024
315103c
Update lilya
tarsil Dec 9, 2024
8d74a71
Fix pydantic versions
tarsil Dec 9, 2024
e29d9f4
Fix pydantic versions
tarsil Dec 9, 2024
735c092
Add docs for simple fake auth
tarsil Dec 9, 2024
e091c33
Merge branch 'main' into feat/oauth
tarsil Dec 9, 2024
6a8cd2e
Simple security docs
tarsil Dec 10, 2024
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
2 changes: 0 additions & 2 deletions docs/en/docs/references/injector.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ from esmerald import Inject, Injects, Factory, DiderectInjects
- "!^__call__"
- "!^__eq__"

::: esmerald.DirectInjects

::: esmerald.Factory
options:
filters:
Expand Down
80 changes: 80 additions & 0 deletions docs/en/docs/security/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +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
Loading