diff --git a/Makefile b/Makefile index 9de29e1..3dfab37 100644 --- a/Makefile +++ b/Makefile @@ -2,4 +2,10 @@ dev: docker-compose up -d dev-down: - docker-compose down \ No newline at end of file + docker-compose down + +start-server: + uvicorn app.main:app --reload + +install-modules: + pip install fastapi[all] fastapi-mail fastapi-jwt-auth[asymmetric] passlib[bcrypt] pymongo \ No newline at end of file diff --git a/app/database.py b/app/database.py index ed87d09..fad8ae7 100644 --- a/app/database.py +++ b/app/database.py @@ -2,8 +2,14 @@ import pymongo from app.config import settings -client = mongo_client.MongoClient(settings.DATABASE_URL) -print('Connected to MongoDB...') +client = mongo_client.MongoClient( + settings.DATABASE_URL, serverSelectionTimeoutMS=5000) + +try: + conn = client.server_info() + print(f'Connected to MongoDB {conn.get("version")}') +except Exception: + print("Unable to connect to the MongoDB server.") db = client[settings.MONGO_INITDB_DATABASE] User = db.users diff --git a/app/oauth2.py b/app/oauth2.py index 517960f..11146cd 100644 --- a/app/oauth2.py +++ b/app/oauth2.py @@ -5,7 +5,7 @@ from pydantic import BaseModel from bson.objectid import ObjectId -from app.serializers import userEntity +from app.serializers.userSerializers import userEntity from .database import User from .config import settings diff --git a/app/routers/auth.py b/app/routers/auth.py index 5187a0f..a829d09 100644 --- a/app/routers/auth.py +++ b/app/routers/auth.py @@ -1,11 +1,10 @@ from datetime import datetime, timedelta from bson.objectid import ObjectId -from fastapi import APIRouter, Request, Response, status, Depends, HTTPException -from pydantic import EmailStr +from fastapi import APIRouter, Response, status, Depends, HTTPException from app import oauth2 from app.database import User -from app.serializers import userEntity, userResponseEntity +from app.serializers.userSerializers import userEntity, userResponseEntity from .. import schemas, utils from app.oauth2 import AuthJWT from ..config import settings @@ -43,15 +42,11 @@ async def create_user(payload: schemas.CreateUserSchema): @router.post('/login') def login(payload: schemas.LoginUserSchema, response: Response, Authorize: AuthJWT = Depends()): # Check if the user exist - user = userEntity(User.find_one({'email': payload.email.lower()})) - if not user: + db_user = User.find_one({'email': payload.email.lower()}) + if not db_user: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Incorrect Email or Password') - - # Check if user verified his email - if not user['verified']: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, - detail='Please verify your email address') + user = userEntity(db_user) # Check if the password is valid if not utils.verify_password(payload.password, user['password']): diff --git a/app/routers/user.py b/app/routers/user.py index ae1c722..46a5917 100644 --- a/app/routers/user.py +++ b/app/routers/user.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, Depends from bson.objectid import ObjectId -from app.serializers import userResponseEntity +from app.serializers.userSerializers import userResponseEntity from app.database import User from .. import schemas, oauth2 diff --git a/app/schemas.py b/app/schemas.py index ab5166e..fa2f692 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -3,7 +3,6 @@ class UserBaseSchema(BaseModel): - id: str | None = None name: str email: str photo: str @@ -26,6 +25,11 @@ class LoginUserSchema(BaseModel): password: constr(min_length=8) +class UserResponseSchema(UserBaseSchema): + id: str + pass + + class UserResponse(BaseModel): status: str - user: UserBaseSchema + user: UserResponseSchema diff --git a/app/serializers.py b/app/serializers/userSerializers.py similarity index 80% rename from app/serializers.py rename to app/serializers/userSerializers.py index 9a82e3f..fc25f84 100644 --- a/app/serializers.py +++ b/app/serializers/userSerializers.py @@ -24,5 +24,14 @@ def userResponseEntity(user) -> dict: } +def embeddedUserResponse(user) -> dict: + return { + "id": str(user["_id"]), + "name": user["name"], + "email": user["email"], + "photo": user["photo"] + } + + def userListEntity(users) -> list: return [userEntity(user) for user in users] diff --git a/readMe.md b/readMe.md new file mode 100644 index 0000000..3137be8 --- /dev/null +++ b/readMe.md @@ -0,0 +1,41 @@ +# API with Python, FastAPI, and MongoDB: JWT Authentication + +This article will teach you how to add JSON Web Token (JWT) authentication to your FastAPI app using PyMongo, Pydantic, FastAPI JWT Auth package, and Docker-compose. + +![API with Python, FastAPI, and MongoDB: JWT Authentication](https://codevoweb.com/wp-content/uploads/2022/07/API-with-Python-FastAPI-and-MongoDB-JWT-Authentication.webp) + +## Topics Covered + +- How to Setup FastAPI with MongoDB +- Starting the FastAPI Server +- Set up Environment Variables with Pydantic +- Connect to the MongoDB Database +- Creating the Schemas with Pydantic +- Create Serializers for the MongoDB BSON Documents +- Password Management in FastAPI +- Creating Utility Functions to Sign and Verify JWTs +- Creating the Authentication Controllers in FastAPI + - User Registration Handler + - User Sign-in Handler + - Refresh Access Token Handler + - Sign out User Handler +- How to Protect Private Routes +- Creating a User Handler +- Adding the API Routes and CORS +- Testing the API with Postman + +Read the entire article here: [https://codevoweb.com/api-with-python-fastapi-and-mongodb-jwt-authentication](https://codevoweb.com/api-with-python-fastapi-and-mongodb-jwt-authentication) + +Articles in this series: + +### 1. API with Python, FastAPI, and MongoDB: JWT Authentication + +[API with Python, FastAPI, and MongoDB: JWT Authentication](https://codevoweb.com/api-with-python-fastapi-and-mongodb-jwt-authentication) + +### 2. Build API with Python & FastAPI: SignUp User and Verify Email + +[Build API with Python & FastAPI: SignUp User and Verify Email](https://codevoweb.com/api-with-python-fastapi-signup-user-and-verify-email) + +### 3. CRUD RESTful API Server with Python, FastAPI, and MongoDB + +[CRUD RESTful API Server with Python, FastAPI, and MongoDB](https://codevoweb.com/crud-restful-api-server-with-python-fastapi-and-mongodb) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0630b1c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,42 @@ +aiosmtplib==1.1.7 +anyio==3.6.2 +autopep8==2.0.1 +bcrypt==4.0.1 +blinker==1.5 +certifi==2022.12.7 +cffi==1.15.1 +click==8.1.3 +colorama==0.4.6 +cryptography==3.4.8 +dnspython==2.2.1 +email-validator==1.3.0 +fastapi==0.87.0 +fastapi-jwt-auth==0.5.0 +fastapi-mail==1.2.2 +h11==0.14.0 +httpcore==0.16.3 +httptools==0.5.0 +httpx==0.23.1 +idna==3.4 +itsdangerous==2.1.2 +Jinja2==3.1.2 +MarkupSafe==2.1.1 +orjson==3.8.3 +passlib==1.7.4 +pycodestyle==2.10.0 +pycparser==2.21 +pydantic==1.10.2 +PyJWT==1.7.1 +pymongo==4.3.3 +python-dotenv==0.21.0 +python-multipart==0.0.5 +PyYAML==6.0 +rfc3986==1.5.0 +six==1.16.0 +sniffio==1.3.0 +starlette==0.21.0 +typing_extensions==4.4.0 +ujson==5.6.0 +uvicorn==0.20.0 +watchfiles==0.18.1 +websockets==10.4