如何使用JavaScript Fetch API在用户登录后将其重定向到另一个页面?

0md85ypi  于 2023-03-06  发布在  Java
关注(0)|答案(2)|浏览(345)

使用以下JavaScript代码,我请求获取firebase令牌,然后使用JavaScript fetch()方法向FastAPI后端发出POST请求,以便用户登录,然后在后端(如下所示)检查令牌是否有效,如果有效,则返回重定向(即RedirectResponse)到另一个网页,问题是浏览器中的重定向不起作用,上一页仍然存在。

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));

         
        })

返回登录页面的后端端点,该页面包含带有按钮和loginGoogle函数的HTML代码:

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

我称之为POST端点,然后重定向到/1,这是一个GET路由,status_code303,这就是@tiangolo在文档中指定它从POST重定向到GET路由的方式。

@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"}

这是用户应该重定向到的GET端点,但它没有:

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

测试/login终点的Swagger屏幕截图:

aoyhnmkz

aoyhnmkz1#

我看到的可能导致这不起作用的主要“问题”是从Post请求到Get请求的速度太快。
在网上搜索了一下之后,我偶然发现了这个[BUG] RedirectResponse from a POST request route to GET request route,如果你读到这个bug,你会看到他们指定有时你可能需要一个307,而不是你可以在这里读到关于307响应的307 Temporary Redirect
根据这一点,以下内容应有所帮助:

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})

据我所知,这里的解决方案是使用status_code=status.HTTP_302_FOUND,您可以在这里了解更多信息:What Is a 302 Status Code?
您也可以参考以下链接了解更多信息:

  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
    1.重定向响应
    根据评论中的@Chris,你还有以下几点:
7vhp5slm

7vhp5slm2#

选项1-返回RedirectResponse

当使用fetch()向服务器发出HTTP请求,而服务器以RedirectResponse响应时,重定向响应将在客户端自动执行(如here所述),因为fetch()方法中redirect模式默认设置为follow,这意味着用户不会被重定向到新的URL。而fetch()将在幕后跟踪该重定向并返回重定向URL的响应。您可能希望将redirect设置为manual将允许您获取重定向URL(包含在Location响应头中)并手动导航到新页面,但事实并非如此,如here所述。
但是,您仍然可以在fetch()请求中使用默认的redirect值,即follow(不需要手动指定,因为已经默认设置了它-在下面的示例中,仅为了清楚起见手动定义它),然后使用Response.redirected检查响应是否是您所做的重定向请求的结果。如果是,则可以使用Response.url,这将返回 "在任何重定向之后获得的最终URL",并且使用JavaScript的window.location.href,您可以将用户重定向到目标URL(即重定向页面)。
除了window.location.href,用户还可以使用window.location.replace()。与设置href属性值的区别在于,当使用location.replace()方法时,在导航到给定的URL后,当前页面不会保存在会话history中-这意味着用户将无法使用后退按钮导航到它。

工作示例

    • 应用程序. 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'

模板/索引. 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;  // or, location.replace(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="user@mail.com"><br>
         <label for="password">Password:</label><br>
         <input type="password" id="password" name="password" value="pa55w0rd"><br><br>
         <input type="submit" value="Submit" class="submit">
      </form>
      <div id="response"></div>
   </body>
</html>

选项2-返回包含重定向URL的JSON响应

您可以让服务器返回一个普通的JSON响应,并将URL包含在JSON对象中,而不是从服务器返回一个RedirectResponse。在客户端,您可以检查服务器返回的JSON对象(作为fetch()请求的结果)是否包含url键,如果包含,则检索其值并将用户重定向到目标URL。使用JavaScript的window.location.hrefwindow.location.replace()
或者,可以将重定向URL添加到服务器端的自定义response header(参见示例herehere,了解如何在FastAPI中设置响应头),并在使用fetch()发布请求后在客户端访问它,如下所示(请注意,如果您正在执行cross-origin请求,则必须在服务器端设置Access-Control-Expose-Headers响应头(参见示例herehere,以及FastAPI关于如何使用expose_headers参数的CORSMiddleware文档),指出您的自定义响应头(包括重定向URL)应该对浏览器中运行的JS脚本可用,因为默认情况下只公开CORS-safelisted response headers)。

工作示例

    • 应用程序. py**
from fastapi import FastAPI, Request, status, Depends
from fastapi.templating import Jinja2Templates
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 {'url': '/welcome'}
    else:
        return 'Validation failed'
 

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

模板/索引. 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',
                    body: data,
                 })
                 .then(res => res.json())
                 .then(data => {
                    if (data.url)
                       window.location.href = data.url; // or, location.replace(data.url);
                    else
                       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="user@mail.com"><br>
         <label for="password">Password:</label><br>
         <input type="password" id="password" name="password" value="pa55w0rd"><br><br>
         <input type="submit" value="Submit" class="submit">
      </form>
      <div id="response"></div>
   </body>
</html>

选项3-在前端使用HTML <form>

如果您的项目不需要使用fetch()请求,您可以使用普通的HTML <form>,让用户单击submit按钮将POST请求发送到服务器。在服务器端使用RedirectResponse(如选项1所示)将导致客户端的用户自动重定向到目标URL,而无需任何进一步的操作。

相关问题