I have two endpoints, /login and /index. The /login is for authentication and getting an access_token. The /index is protected and user should login first.
After authentication, the /login sends the access token to the client. The client should be redirect to the /index page after receiving an access token.
It is the code:
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")
@app.get("/index")
async def root(
request: Request,
current_user: Annotated[User, Depends(oauth2_scheme)]
):
return templates.TemplateResponse("index.html", {"request": request})
@app.post("/login")
async def login(
request: Request,
data: Annotated[OAuth2PasswordRequestForm, Depends()],
):
user = authenticate_user(data.username, data.password)
if not user:
raise HTTPException(...)
access_token = create_access_token(...)
response.set_cookie(key="access_token", value=access_token, httponly=True)
return Token(access_token=access_token, token_type="bearer")
It is the client form:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form id="loginForm" >
<table>
<tr>
<td>
<label for="username">Email:</label>
</td>
<td>
<input type="text" id="username" name="username" required>
</td>
</tr>
<tr>
<td>
<label for="password">Pass Code:</label>
</td>
<td>
<input type="text" id="password" name="password" required>
</td>
</tr>
<tr>
<td></td>
<td>
<button type="submit" style="margin-top: 15px">Submit</button>
</td>
</tr>
</table>
</form>
<script>
document.getElementById("loginForm").addEventListener("submit", function (event) {
event.preventDefault();
fetch("/login", {
method: "POST",
body: new FormData(event.target)
})
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Failed to authenticate');
}
})
.then(data => {
window.location.href = '/index';
})
.catch(error => console.error("Error:", error));
});
</script>
</body>
</html>
This code does not work because of lacking of the authentication header. I get this error:
{"detail":"Not authenticated"}
The test in /docs works because it sends the access token in the Authorization header. This command works:
curl -X 'GET'
'http://127.0.0.1:8000/index'
-H 'accept: application/json'
-H 'Authorization: Bearer A_VALID_TOKEN '
I do not know how I should handle the client side. I am not sure if I should send another fetch for the /index and get the html content and assign it to the body section. I do not know what is the best practice. Maybe I can use RedirectResponse from fastapi.response. I am not sure if it is a good practice or not. /login should send back an access token and not a html code I think.
Edit
The access token is stored in cookie.
2
Answers
You’re on the right track. The typical flow for handling authentication and redirecting after successful login is as follows:
Here’s how you can modify your client-side code:
This modified client-side code includes a change to use type="password" for the password input field and stores the received access token in a cookie named access_token. It then redirects the user to the protected /index endpoint.
On the server side, you should modify the /index endpoint to validate the access token from the request headers using the oauth2_scheme. Make sure you include the access token in the Authorization header when making requests to the protected endpoint.
This approach aligns with best practices for handling authentication and authorization in web applications.
You can send a bearer token with
fetch
:But this will not redirect. In your
then
you are aiming to redirect, but you have two main options here. Either you have a session id or something stored in the cookies, representing a valid session, in which case there should be no issues with the redirect, or you want to actually send the bearer token with a redirect, in which case you will need to send (and receive) this value as a POST parameter rather than a request header.I’ve been researching this for a JWT SSO work and we finally decided to use a POST parameter in order to pass a JWT with the redirect rather than passing it as a request header.
EDIT
Example of the
form
idea:and you should submit this form when the login succeeded. Maybe you do a login with the first
fetch
call you have, receive the token, set the value of your token field in this second form andsubmit()
the form object.