I have an app that is written with FastAPI and SvelteKit, both these is running in separate containers, the current solution is Svelte sends the username and password to the FastAPI server, the FastAPI server then returns a signed JWT to svelte which is used to authenticate with FastAPI.
Heres the login flow Svelte -> username + password -> FastAPI
FastAPI -> JWT -> Svelte(stored in cookie)
Here's what happens when a request is made Svelte(Authorization: Bearer) -> FastAPI
I want to get rid of my own username and password and use KeyCloak for my authentication. I'm super new to OAuth so I have no idea what I'm supposed to do or even what terms to search.
Here's what I understand I want: Svelte(goto "/login") -> redirects to keycloak -> login and get a token (somehow get the token to FastAPI so I can sign my own token and send it to svelte)
And when I make a request Svelte -> FastAPI Token -> FastAPI
I crafted some Python code for fastAPI with keycloak integration, it may be helpful to share it.
Declare auth functions
#/auth.py
from fastapi.security import OAuth2AuthorizationCodeBearer
from keycloak import KeycloakOpenID # pip require python-keycloak
from config import settings
from fastapi import Security, HTTPException, status,Depends
from pydantic import Json
from models import User
# This is used for fastapi docs authentification
oauth2_scheme = OAuth2AuthorizationCodeBearer(
authorizationUrl=settings.authorization_url, # https://sso.example.com/auth/
tokenUrl=settings.token_url, # https://sso.example.com/auth/realms/example-realm/protocol/openid-connect/token
)
# This actually does the auth checks
# client_secret_key is not mandatory if the client is public on keycloak
keycloak_openid = KeycloakOpenID(
server_url=settings.server_url, # https://sso.example.com/auth/
client_id=settings.client_id, # backend-client-id
realm_name=settings.realm, # example-realm
client_secret_key=settings.client_secret, # your backend client secret
verify=True
)
async def get_idp_public_key():
return (
"-----BEGIN PUBLIC KEY-----\n"
f"{keycloak_openid.public_key()}"
"\n-----END PUBLIC KEY-----"
)
# Get the payload/token from keycloak
async def get_payload(token: str = Security(oauth2_scheme)) -> dict:
try:
return keycloak_openid.decode_token(
token,
key= await get_idp_public_key(),
options={
"verify_signature": True,
"verify_aud": False,
"exp": True
}
)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=str(e), # "Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
# Get user infos from the payload
async def get_user_info(payload: dict = Depends(get_payload)) -> User:
try:
return User(
id=payload.get("sub"),
username=payload.get("preferred_username"),
email=payload.get("email"),
first_name=payload.get("given_name"),
last_name=payload.get("family_name"),
realm_roles=payload.get("realm_access", {}).get("roles", []),
client_roles=payload.get("realm_access", {}).get("roles", [])
)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e), # "Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
Define your models , you can use what ever model you need :
#/models.py
from pydantic import BaseModel, EmailStr
class User(BaseModel):
id: str
username: str
email: str
first_name: str
last_name: str
realm_roles: list
client_roles: list
class authConfiguration(BaseModel):
server_url: str
realm: str
client_id: str
client_secret: str
authorization_url: str
token_url: str
Define your config:
#/config.py
from models import authConfiguration
settings = authConfiguration(
server_url="http://localhost:8080/",
realm="roc",
client_id="rns:roc:portal",
client_secret="",
authorization_url="http://localhost:8080/realms/roc/protocol/openid-connect/auth",
token_url="http://localhost:8080/realms/roc/protocol/openid-connect/token",
)
and finally define your routes and secure them:
#/main.py
import uvicorn
from fastapi import FastAPI,Depends
from models import User
from auth import get_user_info
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.get("/secure")
async def root(user: User = Depends(get_user_info)):
return {"message": f"Hello {user.username} you have the following service: {user.realm_roles}"}
if __name__ == '__main__':
uvicorn.run('main:app', host="127.0.0.1", port=8081)
On the keycloak side create a realm and within this real create a client. Create a test user to use it for authentification and that's it!
References:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With