Why Care About Auth?
-
Authentication (AuthN): Confirms users are who they say they are (e.g., login).
-
Authorization (AuthZ): Decides what users can do (e.g., "Admins can delete posts").
We’ll build both using FastAPI (backend), GraphQL (API layer), and MySQL (database).
Step 1: Setup
Install fast Api and other required package.
pip install fastapi strawberry-graphql sqlalchemy pymysql passlib python-jose[python-jwt] uvicorn
Folder Structure:
├── main.py
├── database.py
├── models.py
├── schemas.py
└── auth.py
Step 2: MySQL & SQLAlchemy Models
File: database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "mysql+pymysql://user:password@localhost/mydb"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
File: models.py
from sqlalchemy import Column, Integer, String, Boolean
from database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String(100), unique=True, index=True)
hashed_password = Column(String(200))
is_active = Column(Boolean, default=True)
role = Column(String(20), default="user") # "user" or "admin"
Run this in Python shell to create tables:
from database import engine
from models import Base
Base.metadata.create_all(bind=engine)
Step 3: Authentication (Login/Signup)
File: auth.py
from passlib.context import CryptContext
from jose import JWTError, jwt
from datetime import datetime, timedelta
SECRET_KEY = "your-secret-key" # Change this!
ALGORITHM = "HS256"
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(password: str):
return pwd_context.hash(password)
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def create_access_token(data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
Step 4: GraphQL Setup with Strawberry
File: schemas.py
import strawberry
from typing import Optional
@strawberry.type
class UserType:
id: int
email: str
role: str
@strawberry.input
class LoginInput:
email: str
password: str
@strawberry.input
class SignupInput:
email: str
password: str
Step 5: Auth in GraphQL Resolvers
File: main.py
from fastapi import FastAPI, Depends, HTTPException
from strawberry.fastapi import GraphQLRouter
import strawberry
from database import SessionLocal
from models import User
from auth import hash_password, verify_password, create_access_token, SECRET_KEY
from jose import jwt
import schemas
# Dependency to get DB session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@strawberry.type
class Mutation:
@strawberry.mutation
async def signup(self, input: schemas.SignupInput, db: Session = Depends(get_db)) -> str:
# Check if user exists
existing_user = db.query(User).filter(User.email == input.email).first()
if existing_user:
raise HTTPException(status_code=400, detail="Email already registered")
hashed_pw = hash_password(input.password)
new_user = User(email=input.email, hashed_password=hashed_pw)
db.add(new_user)
db.commit()
return "User created!"
@strawberry.mutation
async def login(self, input: schemas.LoginInput, db: Session = Depends(get_db)) -> str:
user = db.query(User).filter(User.email == input.email).first()
if not user or not verify_password(input.password, user.hashed_password):
raise HTTPException(status_code=401, detail="Invalid credentials")
token = create_access_token(data={"sub": user.email, "role": user.role})
return token
# Protected Query Example
@strawberry.type
class Query:
@strawberry.field
async def protected_data(self, token: str) -> str:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
if payload["role"] != "admin":
raise HTTPException(status_code=403, detail="Permission denied")
return "Top-secret admin data!"
except JWTError:
raise HTTPException(status_code=401, detail="Invalid token")
schema = strawberry.Schema(Query, Mutation)
graphql_app = GraphQLRouter(schema)
app = FastAPI()
app.include_router(graphql_app, prefix="/graphql")
Step 6: Test Your API
-
Signup:
mutation {
signup(input: {email: "test@user.com", password: "secret123"})
}
-
Login:
mutation {
login(input: {email: "test@user.com", password: "secret123"})
}
Copy the returned JWT token.
-
Access Protected Data:
query {
protectedData(token: "PASTE_JWT_HERE")
}
(Admins will see data; users get "Permission denied")
Key Takeaways
-
Hashing: Never store plain-text passwords.
-
JWT: Tokens verify user identity. Include
role
in the token payload for authorization. -
AuthZ: Check the user’s role/permissions before executing sensitive operations.
Common Pitfalls
-
🔐 Change
SECRET_KEY
in production! -
⏱ Shorten JWT expiry (15-30 mins) + use refresh tokens.
-
🛡 Add HTTPS to prevent token theft.
Login to leave a comment.