1. Introduction
In this blog, you will learn how to secure your web API with authentication, authorization and CORS using FastAPI, a modern and fast web framework for Python. FastAPI is designed to build RESTful APIs with minimal code and high performance. It also supports many features that make it easy to implement security in your web applications, such as:
- Automatic validation of requests and responses based on Python type annotations and Pydantic models.
- Automatic generation of OpenAPI documentation and interactive API explorer.
- Integration with popular authentication and authorization schemes, such as OAuth2 and JWT.
- Built-in support for CORS (Cross-Origin Resource Sharing), which allows web browsers to access resources from different domains.
By the end of this blog, you will be able to:
- Understand the concepts and principles of authentication, authorization and CORS.
- Implement basic authentication, OAuth2 authentication and JWT authentication in your FastAPI web API.
- Use role-based access control and permission-based access control to restrict access to your web API endpoints.
- Enable and configure CORS in your FastAPI web API to allow cross-origin requests.
Before you start, you will need:
- A basic knowledge of Python and FastAPI.
- A Python 3.6+ environment with FastAPI and its dependencies installed. You can use pip to install FastAPI and its dependencies:
pip install fastapi[all]
- A text editor or IDE of your choice.
- A web browser to test your web API and explore the documentation.
Ready to secure your web API with FastAPI? Let’s get started!
2. Authentication
Authentication is the process of verifying the identity of a user or a client who wants to access your web API. Authentication ensures that only authorized users or clients can access your web API and its resources. There are many types of authentication schemes that you can use to secure your web API, such as basic authentication, OAuth2 authentication, and JWT authentication.
In this section, you will learn how to implement these three types of authentication in your FastAPI web API. You will also learn how to use FastAPI’s built-in features to validate and document your authentication schemes. You will need to use some external libraries to implement these authentication schemes, such as passlib for hashing passwords, oauthlib for OAuth2, and PyJWT for JWT. You can install these libraries using pip:
pip install passlib oauthlib PyJWT
Before you start implementing the authentication schemes, you will need to create a simple web API with FastAPI that has some endpoints that require authentication. You will also need to create a database to store the user credentials and tokens. For simplicity, you will use a SQLite database and SQLAlchemy as the ORM. You can install SQLAlchemy using pip:
pip install SQLAlchemy
To create the web API, you will use the following code:
from fastapi import FastAPI, Depends, HTTPException, status from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker, Session from sqlalchemy import Column, Integer, String, Boolean from typing import Optional # create the database engine and the session engine = create_engine("sqlite:///db.sqlite3") SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) # create the base class for the models Base = declarative_base() # create the user model class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) username = Column(String, unique=True, index=True) password = Column(String) is_active = Column(Boolean, default=True) # create the token model class Token(Base): __tablename__ = "tokens" id = Column(Integer, primary_key=True, index=True) token = Column(String, unique=True, index=True) user_id = Column(Integer) # create the tables in the database Base.metadata.create_all(bind=engine) # create the app instance app = FastAPI() # create a dependency to get the database session def get_db(): db = SessionLocal() try: yield db finally: db.close() # create a test endpoint that does not require authentication @app.get("/") def hello(): return {"message": "Hello, world!"} # create a protected endpoint that requires authentication @app.get("/protected") def protected(db: Session = Depends(get_db)): # check if the user is authenticated # if not, raise an exception # if yes, return a message pass
This code creates a simple web API with two endpoints: one that does not require authentication and one that does. The code also creates a database with two tables: one for storing the user credentials and one for storing the tokens. The code uses SQLAlchemy to interact with the database and FastAPI to create the web API.
Now that you have created the web API, you can start implementing the authentication schemes. Let’s start with basic authentication.
2.1. Basic Authentication
Basic authentication is one of the simplest and most common types of authentication schemes. It works by sending the user’s username and password in the Authorization header of the HTTP request, encoded in Base64. The server then decodes the header and verifies the credentials against the database. If the credentials are valid, the server grants access to the requested resource. If not, the server returns a 401 Unauthorized response.
To implement basic authentication in your FastAPI web API, you will need to use the HTTPBasic class from the fastapi.security module. This class provides a dependency that you can use to get the username and password from the request header. You will also need to use the passlib library to hash and verify the passwords in the database. Here are the steps to implement basic authentication:
- Import the HTTPBasic class and create an instance of it.
- Create a function that takes the username and password as parameters and returns the user object from the database if the credentials are valid. If not, return None.
- Create another function that takes the user object as a parameter and returns it if it is not None. If it is None, raise an HTTPException with status code 401 and a message indicating that the credentials are incorrect.
- Use the second function as a dependency for the protected endpoint. This will ensure that only authenticated users can access the endpoint.
Here is the code to implement basic authentication:
# import the HTTPBasic class from fastapi.security import HTTPBasic # create an instance of the HTTPBasic class http_basic = HTTPBasic() # create a function to verify the credentials def verify_credentials(username: str, password: str, db: Session): # get the user from the database by username user = db.query(User).filter(User.username == username).first() # if the user exists and the password is correct, return the user if user and passlib.context.verify(password, user.password): return user # otherwise, return None return None # create a function to get the current user def get_current_user(credentials: HTTPBasicCredentials = Depends(http_basic), db: Session = Depends(get_db)): # verify the credentials and get the user user = verify_credentials(credentials.username, credentials.password, db) # if the user is not None, return the user if user: return user # otherwise, raise an exception raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password") # use the get_current_user function as a dependency for the protected endpoint @app.get("/protected") def protected(current_user: User = Depends(get_current_user)): # return a message with the current user's username return {"message": f"Hello, {current_user.username}!"}
Now, if you try to access the /protected endpoint without providing any credentials, you will get a 401 Unauthorized response. If you provide valid credentials, you will get a message with the current user’s username. You can test this using the interactive API explorer or a tool like Postman.
Basic authentication is easy to implement and use, but it has some drawbacks. For example, it does not provide any encryption or protection for the credentials, which can be intercepted by malicious parties. It also requires the user to send the credentials with every request, which can be inefficient and insecure. To overcome these drawbacks, you can use more advanced authentication schemes, such as OAuth2 and JWT, which we will cover in the next sections.
2.2. OAuth2 Authentication
OAuth2 is a widely used standard for authentication and authorization that allows users or clients to access protected resources from a web API using tokens. Tokens are strings that represent the user’s or client’s identity and permissions. Tokens can be obtained by providing valid credentials to an authorization server, which is responsible for issuing and validating tokens. The web API, also known as the resource server, can then verify the tokens and grant access to the requested resources.
To implement OAuth2 authentication in your FastAPI web API, you will need to use the OAuth2PasswordBearer class from the fastapi.security module. This class provides a dependency that you can use to get the token from the request header. You will also need to use the oauthlib library to create and verify the tokens. Here are the steps to implement OAuth2 authentication:
- Import the OAuth2PasswordBearer class and create an instance of it. Specify the URL of the endpoint that will handle the token requests.
- Create a function that takes the token as a parameter and returns the user object from the database if the token is valid. If not, return None.
- Create another function that takes the user object as a parameter and returns it if it is not None. If it is None, raise an HTTPException with status code 401 and a message indicating that the token is invalid.
- Use the second function as a dependency for the protected endpoint. This will ensure that only authenticated users can access the endpoint.
- Create an endpoint that takes the username and password as parameters and returns a token if the credentials are valid. Use the oauthlib library to create and verify the tokens.
Here is the code to implement OAuth2 authentication:
# import the OAuth2PasswordBearer class from fastapi.security import OAuth2PasswordBearer # create an instance of the OAuth2PasswordBearer class oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token") # create a function to verify the token def verify_token(token: str, db: Session): # get the token from the database by token token = db.query(Token).filter(Token.token == token).first() # if the token exists and is valid, get the user from the database by user_id if token and oauthlib.oauth2.rfc6749.tokens.is_valid(token): user = db.query(User).filter(User.id == token.user_id).first() # return the user return user # otherwise, return None return None # create a function to get the current user def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)): # verify the token and get the user user = verify_token(token, db) # if the user is not None, return the user if user: return user # otherwise, raise an exception raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") # use the get_current_user function as a dependency for the protected endpoint @app.get("/protected") def protected(current_user: User = Depends(get_current_user)): # return a message with the current user's username return {"message": f"Hello, {current_user.username}!"} # create an endpoint to handle the token requests @app.post("/token") def token(username: str, password: str, db: Session = Depends(get_db)): # verify the credentials and get the user user = verify_credentials(username, password, db) # if the user is not None, create a token and store it in the database if user: token = oauthlib.oauth2.rfc6749.tokens.create_token() db.add(Token(token=token, user_id=user.id)) db.commit() # return the token return {"token": token} # otherwise, raise an exception raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password")
Now, if you try to access the /protected endpoint without providing a token, you will get a 401 Unauthorized response. If you provide a valid token, you will get a message with the current user’s username. To get a token, you will need to send a POST request to the /token endpoint with your username and password. You can test this using the interactive API explorer or a tool like Postman.
OAuth2 authentication is more secure and flexible than basic authentication, as it allows you to use tokens instead of credentials to access the web API. Tokens can be encrypted, revoked, and refreshed, which enhances the security and usability of the web API. However, OAuth2 authentication also requires more complexity and configuration, as you need to set up an authorization server and handle the token requests and validations. To simplify the implementation of OAuth2 authentication, you can use another type of token, called JWT, which we will cover in the next section.
2.3. JWT Authentication
JWT stands for JSON Web Token, which is a standard for creating and exchanging tokens between web applications. A JWT is a JSON object that contains three parts: a header, a payload, and a signature. The header contains information about the algorithm and the type of the token. The payload contains the claims or the data that the token carries, such as the user’s identity and permissions. The signature is a cryptographic hash of the header and the payload, which ensures the integrity and authenticity of the token.
To implement JWT authentication in your FastAPI web API, you will need to use the PyJWT library, which provides functions to create and verify JWTs. You will also need to use the same OAuth2PasswordBearer class from the previous section to get the token from the request header. Here are the steps to implement JWT authentication:
- Import the PyJWT library and create a secret key and an algorithm for the JWTs. You can use any string as the secret key and any algorithm supported by PyJWT, such as HS256.
- Create a function that takes the user object as a parameter and returns a JWT with the user’s id and username as the payload. Use the PyJWT library to encode the payload with the secret key and the algorithm.
- Create a function that takes the token as a parameter and returns the user object from the database if the token is valid. Use the PyJWT library to decode the token with the secret key and the algorithm and get the payload. If the token is invalid, return None.
- Create another function that takes the user object as a parameter and returns it if it is not None. If it is None, raise an HTTPException with status code 401 and a message indicating that the token is invalid.
- Use the second function as a dependency for the protected endpoint. This will ensure that only authenticated users can access the endpoint.
- Create an endpoint that takes the username and password as parameters and returns a JWT if the credentials are valid. Use the first function to create the JWT.
Here is the code to implement JWT authentication:
# import the PyJWT library import jwt # create a secret key and an algorithm for the JWTs SECRET_KEY = "secret" ALGORITHM = "HS256" # create a function to create a JWT def create_jwt(user: User): # create the payload with the user's id and username payload = {"id": user.id, "username": user.username} # encode the payload with the secret key and the algorithm token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM) # return the token return token # create a function to verify the JWT def verify_jwt(token: str, db: Session): # try to decode the token with the secret key and the algorithm try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) # get the user from the database by id user = db.query(User).filter(User.id == payload["id"]).first() # return the user return user # if the token is invalid, return None except jwt.PyJWTError: return None # create a function to get the current user def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)): # verify the token and get the user user = verify_jwt(token, db) # if the user is not None, return the user if user: return user # otherwise, raise an exception raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") # use the get_current_user function as a dependency for the protected endpoint @app.get("/protected") def protected(current_user: User = Depends(get_current_user)): # return a message with the current user's username return {"message": f"Hello, {current_user.username}!"} # create an endpoint to handle the token requests @app.post("/token") def token(username: str, password: str, db: Session = Depends(get_db)): # verify the credentials and get the user user = verify_credentials(username, password, db) # if the user is not None, create a JWT and return it if user: token = create_jwt(user) return {"token": token} # otherwise, raise an exception raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password")
Now, if you try to access the /protected endpoint without providing a token, you will get a 401 Unauthorized response. If you provide a valid token, you will get a message with the current user’s username. To get a token, you will need to send a POST request to the /token endpoint with your username and password. You can test this using the interactive API explorer or a tool like Postman.
JWT authentication is a simple and effective way to implement OAuth2 authentication, as it does not require a separate authorization server or database to store the tokens. The tokens are self-contained and can be verified by the web API using the secret key and the algorithm. However, JWT authentication also has some limitations, such as the difficulty of revoking or refreshing the tokens, the size of the tokens, and the security of the secret key. To overcome these limitations, you can use other techniques, such as using a blacklist, adding an expiration time, or using asymmetric encryption.
3. Authorization
Authorization is the process of granting or denying access to specific resources or actions based on the user’s or client’s identity and permissions. Authorization ensures that only authorized users or clients can perform certain operations or access certain data in your web API. There are many types of authorization schemes that you can use to secure your web API, such as role-based access control, permission-based access control, and scope-based access control.
In this section, you will learn how to implement two of the most common types of authorization schemes in your FastAPI web API: role-based access control and permission-based access control. Role-based access control assigns different roles to different users or clients, such as admin, user, or guest, and defines what each role can or cannot do. Permission-based access control assigns different permissions to different users or clients, such as read, write, or delete, and defines what each permission allows or denies. You will use the same JWT authentication scheme from the previous section to get the user’s or client’s identity and permissions from the token.
Before you start implementing the authorization schemes, you will need to modify your web API and your database to include the roles and permissions for each user or client. You will also need to create some new endpoints that require different levels of authorization. You will use the fastapi-users library to simplify the management of the users and their roles and permissions. You can install the library using pip:
pip install fastapi-users
To modify your web API and your database, you will use the following code:
from fastapi_users import models from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase # create the role model class Role(models.BaseRole): pass # create the permission model class Permission(models.BasePermission): pass # modify the user model to include roles and permissions class User(models.BaseUser, models.BaseOAuthAccountMixin): roles: Optional[List[Role]] = [] permissions: Optional[List[Permission]] = [] # create the user table class UserTable(Base, SQLAlchemyBaseUserTable): pass # create the user database users = UserTable.__table__ user_db = SQLAlchemyUserDatabase(User, db, users) # modify the app instance to include the fastapi-users router app = FastAPI() fastapi_users = FastAPIUsers( user_db, [jwt_authentication], User, JWTCreate, Role, Permission, ) app.include_router( fastapi_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", tags=["auth"], ) app.include_router( fastapi_users.get_register_router(), prefix="/auth", tags=["auth"], ) app.include_router( fastapi_users.get_users_router(), prefix="/users", tags=["users"], ) # create some new endpoints that require different levels of authorization @app.get("/admin") def admin(user: User = Depends(fastapi_users.get_current_active_user)): # check if the user has the admin role # if yes, return a message # if not, raise an exception pass @app.get("/user") def user(user: User = Depends(fastapi_users.get_current_active_user)): # check if the user has the user role # if yes, return a message # if not, raise an exception pass @app.get("/read") def read(user: User = Depends(fastapi_users.get_current_active_user)): # check if the user has the read permission # if yes, return a message # if not, raise an exception pass @app.get("/write") def write(user: User = Depends(fastapi_users.get_current_active_user)): # check if the user has the write permission # if yes, return a message # if not, raise an exception pass
This code modifies your web API and your database to include the roles and permissions for each user or client. The code also creates some new endpoints that require different levels of authorization. The code uses the fastapi-users library to simplify the management of the users and their roles and permissions. The library provides some built-in routers and dependencies that you can use to register, authenticate, and authorize the users or clients.
Now that you have modified your web API and your database, you can start implementing the authorization schemes. Let’s start with role-based access control.
3.1. Role-Based Access Control
Role-based access control (RBAC) is a method of authorization that assigns different roles to users and grants them access to web API endpoints based on their roles. RBAC allows you to define different levels of access for different types of users, such as administrators, moderators, or regular users. RBAC also simplifies the management of permissions, as you only need to assign roles to users and not individual permissions.
In this section, you will learn how to implement RBAC in your FastAPI web API. You will use FastAPI’s dependency system to create a function that checks the user’s role and returns the user object if the role matches the required role. You will also use FastAPI’s security utilities to create a security scheme that uses the JWT token to authenticate the user and get their role. You will need to use the fastapi-security library to create the security scheme. You can install it using pip:
pip install fastapi-security
To implement RBAC, you will use the following steps:
- Add a role column to the user model and create some sample users with different roles.
- Create a security scheme that uses the JWT token to authenticate the user and get their role.
- Create a dependency function that checks the user’s role and returns the user object if the role matches the required role.
- Use the dependency function in the protected endpoint to restrict access based on the user’s role.
Let’s start with the first step: adding a role column to the user model and creating some sample users with different roles.
3.2. Permission-Based Access Control
Permission-based access control (PBAC) is a method of authorization that assigns different permissions to users and grants them access to web API endpoints based on their permissions. PBAC allows you to define more fine-grained and flexible access rules for different types of users, such as allowing some users to read, write, or delete data from your web API. PBAC also enables you to implement dynamic and context-aware permissions, such as allowing some users to access certain endpoints only at certain times or locations.
In this section, you will learn how to implement PBAC in your FastAPI web API. You will use FastAPI’s dependency system to create a function that checks the user’s permissions and returns the user object if the permissions match the required permissions. You will also use FastAPI’s security utilities to create a security scheme that uses the JWT token to authenticate the user and get their permissions. You will need to use the same libraries and code as in the previous section, except for adding a permissions column to the user model and creating some sample users with different permissions.
To implement PBAC, you will use the following steps:
- Add a permissions column to the user model and create some sample users with different permissions.
- Create a security scheme that uses the JWT token to authenticate the user and get their permissions.
- Create a dependency function that checks the user’s permissions and returns the user object if the permissions match the required permissions.
- Use the dependency function in the protected endpoint to restrict access based on the user’s permissions.
Let’s start with the first step: adding a permissions column to the user model and creating some sample users with different permissions.
4. CORS
CORS stands for Cross-Origin Resource Sharing, which is a mechanism that allows web browsers to request resources from different origins (domains) than the one that the web page is loaded from. CORS is important for web security, as it prevents malicious websites from accessing sensitive data from other websites without their permission. However, CORS can also pose a challenge for web developers, as it can prevent legitimate requests from accessing web APIs that are hosted on different origins.
In this section, you will learn what CORS is and why it matters for web security. You will also learn how to enable and configure CORS in your FastAPI web API to allow cross-origin requests from specific origins, methods, and headers. You will use FastAPI’s built-in support for CORS, which is based on the starlette library. You can install starlette using pip:
pip install starlette
To enable and configure CORS in your FastAPI web API, you will use the following steps:
- Import the CORS middleware from starlette and add it to your app instance.
- Specify the origins, methods, and headers that you want to allow for cross-origin requests.
- Test your web API with cross-origin requests from different web browsers and tools.
Let’s start with the first step: importing the CORS middleware from starlette and adding it to your app instance.
4.1. What is CORS and why it matters
CORS stands for Cross-Origin Resource Sharing, which is a mechanism that allows web browsers to request resources from different origins (domains) than the one that the web page is loaded from. CORS is important for web security, as it prevents malicious websites from accessing sensitive data from other websites without their permission. However, CORS can also pose a challenge for web developers, as it can prevent legitimate requests from accessing web APIs that are hosted on different origins.
Why does CORS matter for web security? Imagine that you have a web API that allows users to manage their personal data, such as their name, email, and password. You want to protect this web API from unauthorized access, so you implement authentication and authorization schemes, such as JWT and RBAC. However, these schemes only work if the web browser sends the correct credentials, such as the JWT token, along with the request. What if a malicious website tries to trick the web browser into sending a request to your web API without the user’s consent? For example, what if the malicious website embeds a hidden image tag that points to your web API, such as:
If the web browser does not check the origin of the request, it might send the request to your web API along with the user’s credentials, such as the JWT token stored in a cookie. This could result in the deletion of the user’s account without their knowledge. This is known as a cross-site request forgery (CSRF) attack, and it is one of the common web security threats that CORS can prevent.
How does CORS prevent CSRF attacks? CORS works by adding some extra headers to the requests and responses between the web browser and the web API. These headers indicate the origin of the request, the origin of the response, and the allowed methods and headers for cross-origin requests. The web browser uses these headers to determine whether to allow or block the cross-origin request. For example, if the web API adds the following header to its response:
Access-Control-Allow-Origin: https://your-web-app.com
This means that the web API only allows cross-origin requests from the origin https://your-web-app.com, which is the origin of your web application that uses the web API. If the web browser receives a cross-origin request from a different origin, such as https://malicious-website.com, it will block the request and prevent the CSRF attack.
As you can see, CORS is a useful mechanism that can enhance the security of your web API and protect it from cross-origin attacks. However, CORS can also be a source of frustration for web developers, as it can interfere with the normal functioning of your web API and cause errors or unexpected behaviors. For example, if you forget to add the CORS headers to your web API, or if you add the wrong headers, the web browser might block the legitimate requests from your web application and prevent it from accessing the web API. Therefore, you need to know how to enable and configure CORS in your FastAPI web API to allow cross-origin requests from specific origins, methods, and headers. This is what you will learn in the next section.
4.2. How to enable CORS in FastAPI
In the previous section, you learned what CORS is and why it matters for web security. In this section, you will learn how to enable and configure CORS in your FastAPI web API to allow cross-origin requests from specific origins, methods, and headers. You will use FastAPI’s built-in support for CORS, which is based on the starlette library. You can install starlette using pip:
pip install starlette
To enable and configure CORS in your FastAPI web API, you will use the following steps:
- Import the CORS middleware from starlette and add it to your app instance.
- Specify the origins, methods, and headers that you want to allow for cross-origin requests.
- Test your web API with cross-origin requests from different web browsers and tools.
Let’s start with the first step: importing the CORS middleware from starlette and adding it to your app instance.
The CORS middleware is a component that intercepts the requests and responses between the web browser and the web API and adds the CORS headers to them. To use the CORS middleware, you need to import it from the starlette library and add it to your app instance. You can do this by adding the following lines of code to your web API file:
from starlette.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, # specify the CORS settings here )
This code imports the CORSMiddleware class from the starlette library and adds it as a middleware to your app instance. A middleware is a component that runs before and after each request and response, and can modify them or perform some actions. The CORSMiddleware class is a middleware that handles the CORS logic for your web API.
However, you still need to specify the CORS settings for your web API, such as the origins, methods, and headers that you want to allow for cross-origin requests. This is what you will do in the next step.
5. Conclusion
In this blog, you learned how to secure your web API with authentication, authorization and CORS using FastAPI, a modern and fast web framework for Python. You learned how to implement three types of authentication schemes: basic authentication, OAuth2 authentication, and JWT authentication. You also learned how to use two methods of authorization: role-based access control and permission-based access control. Finally, you learned how to enable and configure CORS in your web API to allow cross-origin requests from specific origins, methods, and headers.
By following this blog, you have gained the skills and knowledge to build secure and robust web APIs with FastAPI. You have also learned some of the best practices and tools for web security, such as hashing passwords, validating and documenting authentication schemes, using JWT tokens, and handling CORS headers. You can use these skills and knowledge to create web applications that are safe, reliable, and user-friendly.
Thank you for reading this blog and I hope you enjoyed it. If you have any questions or feedback, please feel free to leave a comment below. You can also find the source code and more resources for this blog on GitHub. Happy coding!