Home > Mobile >  How to redirect the user after login using fetch() POST request?
How to redirect the user after login using fetch() POST request?

Time:01-21

using the following JavaScript code, I make a request to obtain the firebase token, and then a POST request using fetch() to my FastAPI backend, in order to login the user. Then, in the backend, as can be seen below, I check whether or not the token is valid, and if so, return a redirect (i.e., RedirectResponse). The problem is that the redirect in the browser does not work, and the previous page remains.

function loginGoogle() {
        var provider = new firebase.auth.GoogleAuthProvider();
        firebase.auth()
            //.currentUser.getToken(provider)
            .signInWithPopup(provider)
            .then((result) => {
                /** @type {firebase.auth.OAuthCredential} */
                var credential = result.credential;

                // This gives you a Google Access Token. You can use it to access the Google API.
                var token = credential.idToken;
            
                // The signed-in user info.
                var user = result.user;
                
                // ...
            })
            .catch((error) => {
                // Handle Errors here.
                var errorCode = error.code;
                var errorMessage = error.message;
                // The email of the user's account used.
                var email = error.email;
                // The firebase.auth.AuthCredential type that was used.
                var credential = error.credential;
                // ...
                
                });

        firebase.auth().currentUser.getIdToken(true).then(function(idToken) {
            console.log(idToken)

            const token = idToken;
            const headers = new Headers({
                    'x-auth-token': token
            });
            const request = new Request('http://localhost:8000/login', {
                    method: 'POST',
                    headers: headers
            });
            fetch(request)
            .then(response => response.json())
            .then(data => console.log(data))
            .catch(error => console.error(error));

         
        })

The endpoint in the backend that returns the login page that contains the html with the button and the loginGoogle function:

@router.get("/entrar")
def login(request: Request):
    return templates.TemplateResponse("login.html", {"request": request})

I call this POST endpoint and then a redirect to /1 which is a GET route, and with status_code being 303, which is how @tiangolo specifies it in the doc to redirect from a POST to a GET route.

@router.post("/login")
async def login(x_auth_token: str = Header(None)):
    valid_token = auth.verify_id_token(x_auth_token)
   
    if valid_token:
        print("token validado")
        return RedirectResponse(url="/1", status_code=status.HTTP_303_SEE_OTHER)
    else:
        return {"msg": "Token no recibido"}

i do redirect a GET

@app.get("/1")
def get_landing(request: Request):
    return templates.TemplateResponse("landing.html", {"request": request})

but does not redirect to any site

Swagger screenshtot enter image description here

CodePudding user response:

When using fetch() to make an HTTP request to a server that responds with a RedirectResponse, the redirect response will be automatically followed (as explained here), as the redirect mode is set to follow by default. This means that the user won't be redirected to the new URL, but rather fetch() will follow that redirect behind the scenes. You might expected that setting redirect to manual instead would allow you to get the redirect Location and manually navigate to the new page, but this is not the case, as described here.

However, you could still use the default redirect value in the fetch() request, i.e., follow (no need to manually specify it, as it is already set by default—in the example below, it is manually defined only for clarity purposes), and then use Response.redirected to check whether or not the response is the result of a request that you made which was redirected. If so, you can use Response.url, which will return the "final URL obtained after any redirects", and using JavaScript's window.location.href, you can redirect the user to the target URL (i.e., the redirect page).

Working Example

app.py

from fastapi import FastAPI, Request, status, Depends
from fastapi.templating import Jinja2Templates
from fastapi.responses import RedirectResponse
from fastapi.security import OAuth2PasswordRequestForm

app = FastAPI()
templates = Jinja2Templates(directory='templates')


@app.get('/')
async def index(request: Request):
    return templates.TemplateResponse('index.html', {'request': request})

    
@app.post('/login')
async def login(data: OAuth2PasswordRequestForm = Depends()):
    # perform some validation, using data.username and data.password
    credentials_valid = True
    
    if credentials_valid:
        return RedirectResponse(url='/welcome',status_code=status.HTTP_302_FOUND)
    else:
        return 'Validation failed'
 

@app.get('/welcome')
async def welcome():
    return 'You have been successfully redirected'

templates/index.html

<!DOCTYPE html>
<html>
   <head>
      <script>
         document.addEventListener("DOMContentLoaded", (event) => {
            document.getElementById("myForm").addEventListener("submit", function (e) {
              e.preventDefault(); // Cancel the default action
              var formElement = document.getElementById('myForm');
              var data = new FormData(formElement);
              fetch('/login', {
                    method: 'POST',
                    redirect: 'follow',
                    body: data,
                 })
                 .then(res => {
                    if (res.redirected) {
                       window.location.href = res.url;
                       return;
                    } 
                    else
                       return res.text();
                 })
                 .then(data => {
                    document.getElementById("response").innerHTML = data;
                 })
                 .catch(error => {
                    console.error(error);
                 });
            });
         });
             
      </script>
   </head>
   <body>
      <form id="myForm">
         <label for="username">Username:</label><br>
         <input type="text" id="username" name="username" value="[email protected]"><br>
         <label for="password">Password:</label><br>
         <input type="password" id="password" name="password" value="pa55w0rd"><br><br>
         <input type="submit" value="Submit" >
      </form>
      <div id="response"></div>
   </body>
</html>

CodePudding user response:

The main "Problem" that I see that might cause this to not work is the fast that you're doing it form a Post request to a Get request.

After some search online I've stumble across this [BUG] RedirectResponse from a POST request route to GET request route if you'll read this bug you'll see they specify that sometimes you may need a 307 instead you can read about the 307 response here 307 Temporary Redirect.

According to this the following should help:

import starlette.status as status
from fastapi.responses import RedirectResponse

@router.post("/login")
async def login(x_auth_token: str = Header(None))
    # Implementation details ...
    return RedirectResponse('/1', status_code=status.HTTP_302_FOUND)

@app.get("/1")
def get_landing(request: Request):
    return templates.TemplateResponse("landing.html", {"request": request})

From what I've seen the solution here was to use status_code=status.HTTP_302_FOUND you can learn more about it here: What Is a 302 Status Code?

You can also refer to the following links for more:

  1. fastapi (starlette) RedirectResponse redirect to post instead get method
  2. How to do a Post/Redirect/Get (PRG) in FastAPI?
  3. [QUESTION] How to post/redirect/get
  4. RedirectResponse

According to @Chris in the comments, you also have the following:

  • Related