K
Kinde•6mo ago
DEX

[Help] 500 With token exchange!

We're building a web application using FastAPI for the backend and React for the frontend. We're trying to integrate Kinde for authentication. What's the best approach to implement this integration considering our tech stack? We're facing issues with the token exchange process. After receiving the authorization code, we're getting a 500 Internal Server Error when trying to exchange it for tokens. Post-authentication, we need to fetch user details and store them in our local PostgreSQL database. What's the recommended way to retrieve user information from Kinde after successful authentication? Are there any specific considerations or best practices we should follow when using Kinde with FastAPI, especially regarding the handling of access tokens and user sessions? Can you provide a sample implementation or code snippet for the authentication flow, particularly the token exchange and user info retrieval parts, that works well with FastAPI?
4 Replies
onderay
onderay•6mo ago
Thanks for reaching out @DEX I will be able to get you a detailed answer on Monday as there is a bit of advice to unpack for you 😃
DEX
DEXOP•6mo ago
Sure, thank you
onderay
onderay•6mo ago
I am going to break out each of your questions into separate messages here for you @DEX For integrating Kinde authentication with a FastAPI backend and React frontend, we can use a combination of Kinde's React SDK for the frontend and a backend approach for FastAPI. Frontend Setup with React SDK For the React frontend, you can use Kinde's React SDK. Here's how to set it up: Install the Kinde React SDK:
npm i @kinde-oss/kinde-auth-react
npm i @kinde-oss/kinde-auth-react
Wrap your application with the KindeProvider:
import {KindeProvider} from "@kinde-oss/kinde-auth-react";

const App = () => (
<KindeProvider
clientId="<your_kinde_client_id>"
domain="<your_kinde_domain>"
logoutUri={window.location.origin}
redirectUri={window.location.origin}
>
<Routes />
</KindeProvider>
);
import {KindeProvider} from "@kinde-oss/kinde-auth-react";

const App = () => (
<KindeProvider
clientId="<your_kinde_client_id>"
domain="<your_kinde_domain>"
logoutUri={window.location.origin}
redirectUri={window.location.origin}
>
<Routes />
</KindeProvider>
);
Implement login and register functionality:
import {useKindeAuth} from '@kinde-oss/kinde-auth-react';

const { login, register } = useKindeAuth();

<button onClick={register} type="button">Sign up</button>
<button onClick={login} type="button">Sign In</button>
import {useKindeAuth} from '@kinde-oss/kinde-auth-react';

const { login, register } = useKindeAuth();

<button onClick={register} type="button">Sign up</button>
<button onClick={login} type="button">Sign In</button>
Backend Setup with FastAPI For the FastAPI backend, you'll need to validate the token received from the frontend. You can follow these general steps, we have a guide here - https://kinde.com/blog/engineering/how-to-protect-your-fastapi-routes-with-kinde-authentication/: Use a JWT validation library compatible with FastAPI to verify the token. Set up an endpoint to receive and validate the token:
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2AuthorizationCodeBearer

app = FastAPI()

oauth2_scheme = OAuth2AuthorizationCodeBearer(
authorizationUrl="https://<your_kinde_domain>/oauth2/auth",
tokenUrl="https://<your_kinde_domain>/oauth2/token"
)

@app.get("/protected")
async def protected_route(token: str = Depends(oauth2_scheme)):
# Validate the token here
# If valid, return protected data
# If invalid, raise HTTPException
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2AuthorizationCodeBearer

app = FastAPI()

oauth2_scheme = OAuth2AuthorizationCodeBearer(
authorizationUrl="https://<your_kinde_domain>/oauth2/auth",
tokenUrl="https://<your_kinde_domain>/oauth2/token"
)

@app.get("/protected")
async def protected_route(token: str = Depends(oauth2_scheme)):
# Validate the token here
# If valid, return protected data
# If invalid, raise HTTPException
To call your API from the React frontend, use the getToken method:
const {getToken} = useKindeAuth();
const fetchData = async () => {
try {
const accessToken = await getToken();
const res = await fetch(`<your-api>`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
const {data} = await res.json();
console.log({data});
} catch (err) {
console.log(err);
}
};
const {getToken} = useKindeAuth();
const fetchData = async () => {
try {
const accessToken = await getToken();
const res = await fetch(`<your-api>`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
const {data} = await res.json();
console.log({data});
} catch (err) {
console.log(err);
}
};
Remember to set up your callback URLs and logout URLs in your Kinde application settings. For additional security, consider registering your API with Kinde and using the audience claim. If you're encountering a 500 Internal Server Error, it could be due to several reasons: Incorrect credentials: Double-check your client_id, client_secret, and redirect_uri. Invalid code: Ensure the authorization code is being correctly extracted and hasn't expired. Mismatched redirect URI: The redirect_uri in the token request must match the one used in the initial authorization request. For security reasons, do not include the client_secret in frontend applications. If you're using a single-page application, consider using the PKCE (Proof Key for Code Exchange) extension. Let me know if the issue still persists for you. For retrieving user information from Kinde after successful authentication using React frontend and FastAPI, you can use the Kinde React SDK on the frontend and make API calls to Kinde's user profile endpoint on the backend. Frontend (React) On the React frontend, you can use the useKindeAuth hook provided by the Kinde React SDK to access user information. Here's how you can retrieve user details:
import { useKindeAuth } from '@kinde-oss/kinde-auth-react';

function UserProfile() {
const { user, isLoading, isAuthenticated } = useKindeAuth();

if (isLoading) {
return <div>Loading...</div>;
}

if (!isAuthenticated) {
return <div>Not authenticated</div>;
}

return (
<div>
<h1>User Profile</h1>
<p>ID: {user.id}</p>
<p>Email: {user.email}</p>
<p>Given Name: {user.given_name}</p>
<p>Family Name: {user.family_name}</p>
</div>
);
}
import { useKindeAuth } from '@kinde-oss/kinde-auth-react';

function UserProfile() {
const { user, isLoading, isAuthenticated } = useKindeAuth();

if (isLoading) {
return <div>Loading...</div>;
}

if (!isAuthenticated) {
return <div>Not authenticated</div>;
}

return (
<div>
<h1>User Profile</h1>
<p>ID: {user.id}</p>
<p>Email: {user.email}</p>
<p>Given Name: {user.given_name}</p>
<p>Family Name: {user.family_name}</p>
</div>
);
}
Backend (FastAPI) On the backend, you can make a request to Kinde's user profile endpoint to retrieve detailed user information. The endpoint is: GET /oauth2/v2/user_profile This endpoint returns the details of the currently logged-in user, including id, names, profile picture URL, and email. To make this request from your FastAPI backend, you'll need to include the access token in the Authorization header. The response will contain the user's details, which you can then store in your PostgreSQL database. Sorry, regarding your question about using Kinde with FastAPI in respect of access tokens and user sessions, I don't have any documentation I can point you towards. Hopefully you can find your answer in my other answers. The following code snippet demonstrates a sample implementation of the authentication flow, including token exchange and user info retrieval, using FastAPI with Kinde.
from pathlib import Path
from fastapi import APIRouter, Request, Depends, HTTPException, status
from fastapi.responses import RedirectResponse, HTMLResponse
from fastapi.templating import Jinja2Templates
from kinde_sdk import Configuration
from kinde_sdk.kinde_api_client import GrantType, KindeApiClient
import uvicorn

configuration = Configuration(host='https://<subdomain>.kinde.com')
kinde_api_client_params = {
"configuration": configuration,
"domain": 'https://<subdomain>.kinde.com',
"client_id": "<CLIENT_ID>",
"client_secret": "<CLIENT_SECRET>",
"grant_type": GrantType.AUTHORIZATION_CODE,
"callback_url": 'http://localhost:4000/callback'
}
kinde_client = KindeApiClient(**kinde_api_client_params)

router = APIRouter()

BASE_DIR = Path(__file__).resolve().parent
templates = Jinja2Templates(directory=str(Path(BASE_DIR, './frontend/templates')))

@router.get("/", response_class=HTMLResponse)
async def root(request: Request):
if kinde_client.is_authenticated():
user_details = kinde_client.get_user_details()
return f"""
<html>
<head><title>Super App</title></head>
<body>
<h1>Super App (with Kinde Login)!</h1>
<p>Welcome {user_details.get("given_name")}!<p>
</body>
</html>
"""
else:
return RedirectResponse(kinde_client.get_login_url())

@router.route("/login")
async def login(request: Request):
return RedirectResponse(kinde_client.get_login_url())

@router.route("/register")
async def register(request: Request):
return RedirectResponse(kinde_client.get_register_url())

@router.get("/callback", response_class=HTMLResponse)
async def callback(request: Request):
kinde_client.fetch_token(authorization_response=str(request.url))
return RedirectResponse("/")

if __name__ == "__main__":
uvicorn.run(router, host="0.0.0.0", port=4000)
from pathlib import Path
from fastapi import APIRouter, Request, Depends, HTTPException, status
from fastapi.responses import RedirectResponse, HTMLResponse
from fastapi.templating import Jinja2Templates
from kinde_sdk import Configuration
from kinde_sdk.kinde_api_client import GrantType, KindeApiClient
import uvicorn

configuration = Configuration(host='https://<subdomain>.kinde.com')
kinde_api_client_params = {
"configuration": configuration,
"domain": 'https://<subdomain>.kinde.com',
"client_id": "<CLIENT_ID>",
"client_secret": "<CLIENT_SECRET>",
"grant_type": GrantType.AUTHORIZATION_CODE,
"callback_url": 'http://localhost:4000/callback'
}
kinde_client = KindeApiClient(**kinde_api_client_params)

router = APIRouter()

BASE_DIR = Path(__file__).resolve().parent
templates = Jinja2Templates(directory=str(Path(BASE_DIR, './frontend/templates')))

@router.get("/", response_class=HTMLResponse)
async def root(request: Request):
if kinde_client.is_authenticated():
user_details = kinde_client.get_user_details()
return f"""
<html>
<head><title>Super App</title></head>
<body>
<h1>Super App (with Kinde Login)!</h1>
<p>Welcome {user_details.get("given_name")}!<p>
</body>
</html>
"""
else:
return RedirectResponse(kinde_client.get_login_url())

@router.route("/login")
async def login(request: Request):
return RedirectResponse(kinde_client.get_login_url())

@router.route("/register")
async def register(request: Request):
return RedirectResponse(kinde_client.get_register_url())

@router.get("/callback", response_class=HTMLResponse)
async def callback(request: Request):
kinde_client.fetch_token(authorization_response=str(request.url))
return RedirectResponse("/")

if __name__ == "__main__":
uvicorn.run(router, host="0.0.0.0", port=4000)
This code sample demonstrates the following key components of the authentication flow: Configuration: The code sets up the Kinde client with the necessary configuration parameters, including the domain, client ID, client secret, and callback URL. Root Route: The root route checks if the user is authenticated. If so, it displays a welcome message with the user's name. If not, it redirects to the login page. Login and Register Routes: These routes redirect the user to the Kinde login and registration pages respectively. Callback Route: This route handles the callback from Kinde after successful authentication. It exchanges the authorization code for an access token using kinde_client.fetch_token(). User Info Retrieval: After successful authentication, the code retrieves user details using kinde_client.get_user_details(). To use this code, you need to install the kinde-python-sdk, fastapi, and uvicorn packages. Also, make sure to replace <subdomain>, <CLIENT_ID>, and <CLIENT_SECRET> with your actual Kinde account details.
DEX
DEXOP•6mo ago
Thanks a lot, let me implement this and update.
Want results from more Discord servers?
Add your server