Building Secure Apps: Authentication & Authorization with FastAPI, GraphQL, and MySQL

nitish
NITISH SHARMA
Published on: June 18, 2025
Updated on: June 22, 2025
Building Secure Apps: Authentication & Authorization with FastAPI, GraphQL, and MySQL blog

Why Care About Auth?

  1. Authentication (AuthN): Confirms users are who they say they are (e.g., login).

  2. 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

  1. Signup:


mutation {
  signup(input: {email: "test@user.com", password: "secret123"})
}
  1. Login:

mutation {
  login(input: {email: "test@user.com", password: "secret123"})
}

Copy the returned JWT token.

  1. Access Protected Data:

query {
  protectedData(token: "PASTE_JWT_HERE")
}

(Admins will see data; users get "Permission denied")

Key Takeaways

  1. Hashing: Never store plain-text passwords.

  2. JWT: Tokens verify user identity. Include role in the token payload for authorization.

  3. 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.

Comments

Login to leave a comment.

Build Software Application with Impact Hive