Mestr FastAPI OAuth2-autentificering! Denne guide dækker password flow, implicit flow, authorization code flow, token-opdatering og bedste sikkerhedspraksis for robuste API'er.
FastAPI OAuth2-implementering: En omfattende guide til autentificeringsflows
I nutidens digitale landskab er det altafgørende at sikre dine API'er. OAuth2 (Open Authorization) er blevet industristandarden for delegeret autorisation, der giver brugere mulighed for at give begrænset adgang til deres ressourcer uden at dele deres legitimationsoplysninger. FastAPI, et moderne, højtydende Python-webframework, gør implementering af OAuth2-autentificering til en leg. Denne omfattende guide vil føre dig gennem de forskellige OAuth2-flows og demonstrere, hvordan du integrerer dem i din FastAPI-applikation, hvilket sikrer, at din API forbliver sikker og tilgængelig.
Forståelse af OAuth2-koncepter
Før vi dykker ned i koden, lad os etablere en klar forståelse af de grundlæggende OAuth2-koncepter:
- Ressourceejer: Brugeren, der ejer dataene og giver adgang.
- Klient: Applikationen, der anmoder om adgang til ressourceejerens data. Dette kan være en webapplikation, mobilapp eller enhver anden tjeneste.
- Autorisationsserver: Autentificerer ressourceejeren og giver autorisation til klienten.
- Ressourceserver: Hostede de beskyttede ressourcer og verificerer adgangstokenet, før adgang gives.
- Adgangstoken: En legitimationsoplysning, der repræsenterer den autorisation, som ressourceejeren har givet klienten.
- Refresh Token: En langvarig legitimationsoplysning, der bruges til at opnå nye adgangstokens uden at kræve, at ressourceejeren genautoriserer.
- Scopes: Definer de specifikke tilladelser, som klienten anmoder om.
OAuth2-flows: Valg af den rette tilgang
OAuth2 definerer flere autorisationsflows, hver især egnet til forskellige scenarier. Her er en oversigt over de mest almindelige flows, og hvornår de skal bruges:
1. Password (Resource Owner Password Credentials) Flow
Beskrivelse: Klienten får direkte adgangstokenet fra autorisationsserveren ved at angive ressourceejerens brugernavn og adgangskode. Anvendelsestilfælde: Meget betroede applikationer, såsom førsteparts mobilapps. Det bør kun bruges, når andre flows ikke er mulige. Fordele: Simpel at implementere. Ulemper: Kræver, at klienten håndterer ressourceejerens legitimationsoplysninger, hvilket øger risikoen for eksponering, hvis klienten kompromitteres. Mindre sikker end andre flows. Eksempel: En virksomheds egen mobilapp, der får adgang til deres interne API.
Implementering i FastAPI:
Først skal du installere de nødvendige pakker:
pip install fastapi uvicorn python-multipart passlib[bcrypt] python-jose[cryptography]
Lad os nu oprette et grundlæggende eksempel:
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
app = FastAPI()
# Replace with a strong, randomly generated secret key
SECRET_KEY = "YOUR_SECRET_KEY"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# Password hashing configuration
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Dummy user database (replace with a real database in production)
users = {
"johndoe": {
"username": "johndoe",
"hashed_password": pwd_context.hash("password123"),
"scopes": ["read", "write"]
}
}
# Function to verify password
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
# Function to create access token
def create_access_token(data: dict, expires_delta: timedelta):
to_encode = data.copy()
expire = datetime.utcnow() + expires_delta
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# OAuth2 endpoint for token generation
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = users.get(form_data.username)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
if not verify_password(form_data.password, user["hashed_password"]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user["username"], "scopes": user["scopes"]},
expires_delta=access_token_expires,
)
return {"access_token": access_token, "token_type": "bearer"}
# Dependency to authenticate requests
async def get_current_user(token: str):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = users.get(username)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(current_user = Depends(get_current_user)):
return current_user
# Example protected endpoint
@app.get("/users/me")
async def read_users_me(current_user = Depends(get_current_active_user)):
return {"username": current_user["username"], "scopes": current_user["scopes"]}
Forklaring:
- Afhængigheder: Vi bruger `fastapi.security.OAuth2PasswordRequestForm` til håndtering af brugernavn og adgangskode.
- Adgangskode-hashing: `passlib` bruges til sikkert at hashe og verificere adgangskoder. Gem aldrig adgangskoder i klartekst!
- JWT-generering: `python-jose` bruges til at oprette og verificere JSON Web Tokens (JWT'er).
- `/token`-endpoint: Dette endpoint håndterer loginprocessen. Det validerer brugernavn og adgangskode, og hvis gyldigt, genererer det et adgangstoken.
- `get_current_user`-afhængighed: Denne funktion verificerer adgangstokenet og henter brugeren.
- `/users/me`-endpoint: Dette er et beskyttet endpoint, der kræver et gyldigt adgangstoken for at få adgang.
2. Implicit Flow
Beskrivelse: Klienten modtager direkte adgangstokenet fra autorisationsserveren, efter at ressourceejeren har autentificeret sig. Adgangstokenet returneres i URL-fragmentet. Anvendelsestilfælde: Single-page applikationer (SPA'er) og andre browserbaserede applikationer, hvor opbevaring af klienthemmeligheder ikke er muligt. Fordele: Simpelt for browserbaserede applikationer. Ulemper: Mindre sikker end andre flows, fordi adgangstokenet er eksponeret i URL'en. Der udstedes ingen refresh token. Eksempel: En JavaScript-applikation, der får adgang til en social medie-API.
Implementeringsovervejelser i FastAPI:
Selvom FastAPI ikke direkte håndterer frontend-aspekterne af det implicitte flow (da det primært er et backend-framework), ville du bruge et frontend-framework som React, Vue eller Angular til at styre autentificeringsflowet. FastAPI ville primært fungere som ressourcesserver.
Forenklet Backend (FastAPI - Ressourcesserver) Eksempel:
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2AuthorizationCodeBearer
from jose import JWTError, jwt
app = FastAPI()
# Replace with a strong, randomly generated secret key
SECRET_KEY = "YOUR_SECRET_KEY"
ALGORITHM = "HS256"
# Dummy user database (replace with a real database in production)
users = {
"johndoe": {
"username": "johndoe",
"scopes": ["read", "write"]
}
}
# OAuth2 scheme - using AuthorizationCodeBearer for token verification
oauth2_scheme = OAuth2AuthorizationCodeBearer(authorizationUrl="/auth", tokenUrl="/token") # These URLs are handled by the Authorization Server (not this FastAPI app).
# Dependency to authenticate requests
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = users.get(username)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(current_user = Depends(get_current_user)):
return current_user
# Example protected endpoint
@app.get("/users/me")
async def read_users_me(current_user = Depends(get_current_active_user)):
return {"username": current_user["username"], "scopes": current_user["scopes"]}
Nøglepunkter for Implicit Flow med FastAPI:
- Autorisationsserverens rolle: Den faktiske autorisation og tokenudstedelse sker på en separat autorisationsserver. FastAPI fungerer som ressourcesserver og validerer tokenet.
- Frontend-håndtering: Frontend-applikationen (f.eks. React, Vue) håndterer omdirigeringen til autorisationsserveren, brugerlogin og hentning af adgangstokenet fra URL-fragmentet.
- Sikkerhedsovervejelser: På grund af eksponeringen af adgangstokenet i URL'en er det afgørende at bruge HTTPS og holde tokenets levetid kort. Det implicitte flow bør undgås, hvis muligt, til fordel for Authorization Code Flow med PKCE.
3. Authorization Code Flow
Beskrivelse: Klienten får først en autorisationskode fra autorisationsserveren, som den derefter udveksler med et adgangstoken. Dette flow involverer en omdirigering fra klienten til autorisationsserveren og tilbage igen. Anvendelsestilfælde: Webapplikationer og mobilapps, hvor en klienthemmelighed kan opbevares sikkert. Fordele: Mere sikker end det implicitte flow, fordi adgangstokenet ikke er direkte eksponeret i browseren. Ulemper: Mere komplekst at implementere end det implicitte flow. Eksempel: En tredjepartsapplikation, der anmoder om adgang til en brugers Google Drev-data.
Authorization Code Flow med PKCE (Proof Key for Code Exchange):
PKCE er en udvidelse til Authorization Code Flow, der mindsker risikoen for aflytning af autorisationskoder. Det anbefales stærkt til mobilapps og SPA'er, da det ikke kræver, at klienten gemmer en hemmelighed.
Implementeringsovervejelser i FastAPI: I lighed med det implicitte flow ville FastAPI primært fungere som ressourcesserver i dette flow. En separat autorisationsserver er ansvarlig for autentificeringen og udstedelsen af autorisationskoden.
Forenklet Backend (FastAPI - Ressourcesserver) Eksempel (svarende til Implicit Flow):
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2AuthorizationCodeBearer
from jose import JWTError, jwt
app = FastAPI()
# Replace with a strong, randomly generated secret key
SECRET_KEY = "YOUR_SECRET_KEY"
ALGORITHM = "HS256"
# Dummy user database (replace with a real database in production)
users = {
"johndoe": {
"username": "johndoe",
"scopes": ["read", "write"]
}
}
# OAuth2 scheme - using AuthorizationCodeBearer for token verification
oauth2_scheme = OAuth2AuthorizationCodeBearer(authorizationUrl="/auth", tokenUrl="/token") # These URLs are handled by the Authorization Server.
# Dependency to authenticate requests
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = users.get(username)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(current_user = Depends(get_current_user)):
return current_user
# Example protected endpoint
@app.get("/users/me")
async def read_users_me(current_user = Depends(get_current_active_user)):
return {"username": current_user["username"], "scopes": current_user["scopes"]}
Nøglepunkter for Authorization Code Flow med PKCE med FastAPI:
- Autorisationsserverens rolle: Autorisationsserveren håndterer generering af autorisationskoden, verifikation af PKCE-kodeverifieren og udstedelse af adgangstokenet.
- Frontend-håndtering: Frontend-applikationen genererer en kodeverifier og kodeudfordring, omdirigerer brugeren til autorisationsserveren, modtager autorisationskoden og udveksler den med et adgangstoken.
- Øget sikkerhed: PKCE forhindrer angreb med aflytning af autorisationskoder, hvilket gør det velegnet til SPA'er og mobilapps.
- Anbefalet tilgang: Authorization Code Flow med PKCE er generelt det mest sikre og anbefalede flow til moderne web- og mobilapplikationer.
4. Client Credentials Flow
Beskrivelse: Klienten autentificerer sig direkte med autorisationsserveren ved hjælp af sine egne legitimationsoplysninger (klient-ID og klienthemmelighed) for at opnå et adgangstoken. Anvendelsestilfælde: Maskine-til-maskine-kommunikation, f.eks. backend-tjenester, der får adgang til hinanden. Fordele: Simpelt for backend-tjenester. Ulemper: Ikke egnet til brugerautentificering. Eksempel: En databehandlingstjeneste, der får adgang til en databasetjeneste.
Implementering i FastAPI:
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from jose import JWTError, jwt
from datetime import datetime, timedelta
app = FastAPI()
# Replace with a strong, randomly generated secret key
SECRET_KEY = "YOUR_SECRET_KEY"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# Dummy client database (replace with a real database in production)
clients = {
"client_id": {
"client_secret": "client_secret",
"scopes": ["read", "write"]
}
}
# Function to create access token
def create_access_token(data: dict, expires_delta: timedelta):
to_encode = data.copy()
expire = datetime.utcnow() + expires_delta
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# HTTP Basic Authentication scheme
security = HTTPBasic()
# Endpoint for token generation
@app.post("/token")
async def login(credentials: HTTPBasicCredentials = Depends(security)):
client = clients.get(credentials.username)
if not client:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect client ID or secret",
headers={"WWW-Authenticate": "Basic"},
)
if credentials.password != client["client_secret"]:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect client ID or secret",
headers={"WWW-Authenticate": "Basic"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": credentials.username, "scopes": client["scopes"]},
expires_delta=access_token_expires,
)
return {"access_token": access_token, "token_type": "bearer"}
# Dependency to authenticate requests
async def get_current_client(token: str):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
client_id: str = payload.get("sub")
if client_id is None:
raise credentials_exception
except JWTError:
raise credentials_exception
client = clients.get(client_id)
if client is None:
raise credentials_exception
return client
async def get_current_active_client(current_client = Depends(get_current_client)):
return current_client
# Example protected endpoint
@app.get("/data")
async def read_data(current_client = Depends(get_current_active_client)):
return {"message": "Data accessed by client: " + current_client["client_secret"]}
Forklaring:
- HTTP Basic autentificering: Vi bruger `fastapi.security.HTTPBasic` til at autentificere klienten.
- `/token`-endpoint: Dette endpoint håndterer klientautentificering. Det validerer klient-ID og hemmelighed, og hvis gyldigt, genererer det et adgangstoken.
- `get_current_client`-afhængighed: Denne funktion verificerer adgangstokenet og henter klienten.
- `/data`-endpoint: Dette er et beskyttet endpoint, der kræver et gyldigt adgangstoken for at få adgang.
Token-opdatering
Adgangstokens har typisk en kort levetid for at minimere virkningen af kompromitterede tokens. Refresh tokens er langlivede legitimationsoplysninger, der kan bruges til at opnå nye adgangstokens uden at kræve, at brugeren genautoriserer.
Implementeringsovervejelser:
- Opbevaring af Refresh Tokens: Refresh tokens bør opbevares sikkert, ideelt krypteret i en database.
- Refresh Token Endpoint: Opret et dedikeret endpoint (f.eks. `/refresh_token`) til at håndtere anmodninger om refresh tokens.
- Tilbagekaldelse af Refresh Tokens: Implementer en mekanisme til at tilbagekalde refresh tokens, hvis de er kompromitterede eller ikke længere er nødvendige.
Eksempel (udvidelse af Password Flow-eksemplet):
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
import secrets # For generating secure random strings
app = FastAPI()
# Replace with a strong, randomly generated secret key
SECRET_KEY = "YOUR_SECRET_KEY"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
REFRESH_TOKEN_EXPIRE_DAYS = 30 # Longer lifetime for refresh tokens
# Password hashing configuration
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Dummy user database (replace with a real database in production)
users = {
"johndoe": {
"username": "johndoe",
"hashed_password": pwd_context.hash("password123"),
"scopes": ["read", "write"],
"refresh_token": None # Store refresh token here
}
}
# Function to verify password (same as before)
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
# Function to create access token (same as before)
def create_access_token(data: dict, expires_delta: timedelta):
to_encode = data.copy()
expire = datetime.utcnow() + expires_delta
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# Function to create refresh token
def create_refresh_token():
return secrets.token_urlsafe(32) # Generate a secure random string
# OAuth2 endpoint for token generation
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = users.get(form_data.username)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
if not verify_password(form_data.password, user["hashed_password"]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
# Create refresh token and store it (securely in a database in real-world)
refresh_token = create_refresh_token()
user["refresh_token"] = refresh_token # Store it in the user object for now (INSECURE for production)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user["username"], "scopes": user["scopes"]},
expires_delta=access_token_expires,
)
return {"access_token": access_token, "token_type": "bearer", "refresh_token": refresh_token}
# Endpoint for refreshing the access token
@app.post("/refresh_token")
async def refresh_access_token(refresh_token: str):
# Find user by refresh token (securely query the database)
user = next((user for user in users.values() if user["refresh_token"] == refresh_token), None)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid refresh token",
headers={"WWW-Authenticate": "Bearer"},
)
# Create a new access token
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user["username"], "scopes": user["scopes"]},
expires_delta=access_token_expires,
)
return {"access_token": access_token, "token_type": "bearer"}
# Dependency to authenticate requests (same as before)
async def get_current_user(token: str):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = next((user for user in users.values() if user["username"] == username), None)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(current_user = Depends(get_current_user)):
return current_user
# Example protected endpoint (same as before)
@app.get("/users/me")
async def read_users_me(current_user = Depends(get_current_active_user)):
return {"username": current_user["username"], "scopes": current_user["scopes"]}
Vigtige sikkerhedsbemærkninger:
- Opbevaring af Refresh Tokens: Eksemplet gemmer refresh tokenet i hukommelsen (usikkert). I et produktionsmiljø skal refresh tokens opbevares sikkert i en database, helst krypteret.
- Refresh Token Rotation: Overvej at implementere refresh token-rotation. Efter at et refresh token er blevet brugt, skal der genereres et nyt refresh token, og det gamle skal annulleres. Dette begrænser virkningen af kompromitterede refresh tokens.
- Revision: Log brugen af refresh tokens for at opdage mistænkelig aktivitet.
Bedste sikkerhedspraksis
Implementering af OAuth2 er kun det første skridt. Overholdelse af bedste sikkerhedspraksis er afgørende for at beskytte din API og brugerdata.
- Brug HTTPS: Brug altid HTTPS til at kryptere kommunikation mellem klienten, autorisationsserveren og ressourcesserveren.
- Valider input: Valider grundigt alle inputdata for at forhindre injektionsangreb.
- Hastighedsbegrænsning: Implementer hastighedsbegrænsning for at forhindre brute-force-angreb.
- Regelmæssig opdatering af afhængigheder: Hold dit FastAPI-framework og alle afhængigheder opdateret for at lappe sikkerhedssårbarheder.
- Brug stærke hemmeligheder: Generer stærke, tilfældige hemmeligheder til dine klienthemmeligheder og JWT-signeringsnøgler. Opbevar disse hemmeligheder sikkert (f.eks. ved hjælp af miljøvariabler eller et hemmelighedsstyringssystem).
- Overvåg og log: Overvåg din API for mistænkelig aktivitet og log alle autentificerings- og autorisationsbegivenheder.
- Håndhæv mindste privilegium: Giv kun klienter de nødvendige tilladelser (scopes).
- Korrekt fejlhåndtering: Undgå at afsløre følsomme oplysninger i fejlmeddelelser.
- Overvej at bruge et gennemtestet OAuth2-bibliotek: I stedet for at implementere OAuth2 fra bunden, overvej at bruge et gennemtestet bibliotek som Authlib. Authlib giver en mere robust og sikker implementering af OAuth2.
Ud over det grundlæggende: Avancerede overvejelser
Når du har en grundlæggende OAuth2-implementering på plads, skal du overveje disse avancerede emner:
- Samtykkestyring: Giv brugere klar og granulær kontrol over de tilladelser, de giver til klienter.
- Delegeret autorisation: Implementer understøttelse af delegeret autorisation, så brugere kan autorisere klienter til at handle på deres vegne.
- Multi-faktor autentificering (MFA): Integrer MFA for at forbedre sikkerheden.
- Fødereret identitet: Understøt autentificering via tredjeparts identitetsudbydere (f.eks. Google, Facebook, Twitter).
- Dynamisk klientregistrering: Tillad klienter at registrere sig dynamisk hos din autorisationsserver.
Konklusion
Implementering af OAuth2-autentificering med FastAPI er en effektiv måde at sikre dine API'er og beskytte brugerdata på. Ved at forstå de forskellige OAuth2-flows, implementere bedste sikkerhedspraksis og overveje avancerede emner kan du bygge robuste og sikre API'er, der opfylder behovene hos dine brugere og applikationer. Husk at vælge det passende flow til dit specifikke brugstilfælde, prioritere sikkerhed og løbende overvåge og forbedre dit autentificeringssystem. Selvom de leverede eksempler viser grundlæggende principper, skal du altid tilpasse dem til dine specifikke krav og konsultere sikkerhedseksperter for en grundig gennemgang.