RBAC Authorization with FastAPI and PropelAuth

RBAC Authorization with FastAPI and PropelAuth

Role based access control (RBAC) is a method of deciding who gets access to what based on their role.

As an example, let's say we are building a product that allows teams to chat with their coworkers. Within a team, each user is either an Admin or a Member. We want to let all users view/send chat messages (Admins and Members), but only Admin's can updating billing information. This is a simple form of RBAC.

Let's take that same example, write it out in code, and explain how it works:

import os
from dotenv import load_dotenv
from fastapi import Depends, FastAPI
from propelauth_fastapi import init_auth, User

load_dotenv()

app = FastAPI()
auth = init_auth(os.getenv("PROPELAUTH_AUTH_URL"), os.getenv("PROPELAUTH_API_KEY"))

@app.get("/api/org/{org_id}/messages")
async def view_messages(org_id: str, current_user: User = Depends(auth.require_user)):
    org = auth.require_org_member(current_user, org_id)
    return fetch_messages(org.org_id)

@app.get("/api/org/{org_id}/billing")
async def view_billing(org_id: str, current_user: User = Depends(auth.require_user)):
    org = auth.require_org_member_with_minimum_role(current_user, org_id, "Admin")
    return fetch_billing_info(org.org_id)

What does init_auth do?

init_auth fetches metadata from PropelAuth that it will use to verify users. It does this once on startup, so that it can verify users without making any external requests.

How does require_user work?

When your frontend makes a request to the backend, it will include a token for the user that made the request. require_user verifies this token (using the metadata it fetched in init_auth), and injects the User into the request. If invalid credentials are provided, the request is rejected.

What is an org_id?

It's an identifier for an organization. PropelAuth provides B2B authentication meaning that your users can create organizations, invite their coworkers to join them, and manage roles within the organization.

Where does the user and org_id come from?

PropelAuth provides end-to-end authentication, and one component of that is a configurable UI (hosted on your domain) for your users to sign up through. Another aspect is a UI for creating/joining organizations, inviting coworkers to those organizations, and letting these users manage permissions within their organization.

A customized UI where a user is managing the Acme Organization

After the user signs up, a secure, HTTP-only cookie is created. We provide frontend libraries so your frontend can understand if a user is currently logged in or not based on that cookie. These libraries can also fetch which organizations the user is a member of, or a short-lived token for the current user - which the backend can verify and understand who made the request.

In React, for example, this might look like this:

import {withAuthInfo} from "@propelauth/react";
import {useEffect, useState} from "react";

// accessToken and orgHelper are automatically injected from withAuthInfo
function AuthenticatedRequestToBilling({accessToken, orgHelper}) {
    const [response, setResponse] = useState(null)

    // A user can be in multiple orgs - get the one they are currently interacting with
    const selectedOrg = orgHelper.useActiveOrg()
    useEffect(() => {
        fetchBilling(accessToken, selectedOrg.orgId).then(setResponse)
    }, [accessToken, selectedOrg.orgId])

    return <span>
        <h2>Server Response</h2>
        <pre>{response ? JSON.stringify(response, null, 2) : "Loading..."}</pre>
    </span>
}

function fetchBilling(accessToken, orgId) {
    return fetch(`/api/org/${orgId}/billing`, {
        headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${accessToken}`
        }
    }).then(response => {
        if (response.ok) {
            return response.json()
        } else {
            return {status: response.status}
        }
    })
}

export default withAuthInfo(AuthenticatedRequestToBilling);

You don't have to worry about managing invitations, designing emails, or dealing with role changes - it's all already provided out of the box.

What happens if the user isn't a member of that organization?

require_org_member will fail and reject the request. This is encoded in the token so it also will not make any external requests.

‍‍

If you are interested in using PropelAuth, you can sign up here or reach out at support@propelauth.com and we'd be happy to answer any questions you have.