skip to Main Content

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


  1. You’re on the right track. The typical flow for handling authentication and redirecting after successful login is as follows:

    1. User submits credentials to the /login endpoint.
    2. Server validates credentials and generates an access token.
    3. Server responds with the access token.
    4. Client receives the access token and stores it securely (usually in a cookie or local storage).
    5. Client initiates a redirect to the protected /index endpoint, including the access token in the Authorization header.
    6. Server verifies the access token and serves the protected content.

    Here’s how you can modify your client-side code:

    <!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="password" 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 => {
                   // Store the access token securely (e.g., in a cookie or local storage)
                   document.cookie = `access_token=${data.access_token}; path=/`;
    
                   // Redirect to the protected /index endpoint
                   window.location.href = '/index';
               })
               .catch(error => console.error("Error:", error));
       });
    </script>
    </body>
    </html>
    

    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.

    Login or Signup to reply.
  2. You can send a bearer token with fetch:

    fetch('your/url', {
      headers: {Authorization: 'Bearer ' + yourtoken}
    })
       .then(data => {
           //some code
       })
    

    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:

    <form action="/index" method="post" style="display: none;">
        <hidden name="token" value="YOUR TOKEN">
    </form>
    

    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 and submit() the form object.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search