Firebase, React project: Client-Server mismatched timezones causing failed post requests
New here? Learn about Bountify and follow @bountify to get notified of new bounties! x

I've got a react component controlling authorized posts to my firestore. Users submit text-based information, and it's sent to firebase using axios as a client. Only problem is that when I try to submit data, I get the following error:
Firebase ID token has expired. Get a fresh ID token from your client app and try again".
However, when I set my computer to the same timezone as the server, the problem is solved and I'm able to submit posts - however this obviously is not a solution.

So - I'm looking for a solution that will allow logged in users to successfully submit posts, whatever timezone they're in. It will need to take into consideration that firebase tokens expire after an hour, and that currently, the mismatch of timezones is making the firestore think they're expired upon return.

Here's the react component controlling the post request:
```
import React from "react";
import Header from "./Header";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";
import axios from "axios";
import * as firebase from 'firebase'
import { auth } from 'firebase/app'
import {Link} from 'react-router-dom'
import UniqueVenueListing from './UniqueVenueListing'
import jwtDecode from 'jwt-decode'

class GigRegister extends React.Component {
  constructor() {
    super();
    this.state = {
      name: "",
      venue: "",
      time: "",
      date: "",
      genre: "",
      tickets: "",
      price: "",
      userDetails: {},
      filterGigs: [],
      isLoggedIn:false,
      currentToken:{}
    };
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.handleClick = this.handleClick.bind(this);
  }

  handleChange(e) {
    this.setState({
      [e.target.name]: e.target.value,
    });
  }

  handleClick() {
    console.log("handle click reached");
    auth()
      .signOut()
      .then(() => {
        console.log("Successfully signed out");
      })
      .catch((err) => {
        console.log(err);
      });
  }

  authListener() {
    auth().onAuthStateChanged((user) => {
      if (user) {
        this.setState(
          {
            userDetails: user
          },
          () =>
            axios
              .get(
                "https://us-central1-gig-fort.cloudfunctions.net/api/getGigListings"
              )
              .then((res) => {
                let filteredGigs = res.data.filter((gig) => {
                  return gig.user === this.state.userDetails.uid;
                });
                this.setState({
                  filterGigs: filteredGigs
                });
              })
        );
      } else {
        this.setState({
          userDetails: null,
        });
        console.log("no user signed in");
      }
    });
  }

  componentDidMount() {
    this.authListener();
  }


  handleSubmit(e) {

    let user = auth().currentUser.uid;
    const gigData = {
      name: this.state.name,
      venue: this.state.venue,
      time: this.state.time,
      date: this.state.date,
      genre: this.state.genre,
      tickets: this.state.tickets,
      price: this.state.price,
      user: user
    };

    auth()
      .currentUser.getIdToken()
      .then(function (token) {
        axios(
          "https://us-central1-gig-fort.cloudfunctions.net/api/createGigListing",
          {
            method: "POST",
            headers: {
              "content-type": "application/json",
              Authorization: "Bearer " + token,
            },
            data: gigData,
          }
        );
      })
      .then((res) => {
        this.props.history.push("/Homepage");
      })
      .catch((err) => {
        console.error(err);
      });
  }

  render() {
    let token1 = localStorage.getItem('token')
    let decodedToken = jwtDecode(token1)
    console.log(`this is the decoded token ${decodedToken.exp}`) 
    return (
      <div className="gig-register">
        <Header />
        <div className="heading-container">
          <h1>Venue Dashboard</h1> <br></br>
          {this.state.userDetails ? (
            <h3>You are signed in as {this.state.userDetails.email}</h3>
          ) : null}
          <div className="gig-reg-buttons">
            {this.state.userDetails ? (
              <Button onClick={this.handleClick}>Sign out </Button>
            ) : (
              <Link to="/" style={{ textDecoration: "none" }}>
                <Button>Sign In</Button>
              </Link>
            )}
            <Link to="/Homepage" style={{ textDecoration: "none" }}>
              <Button>Go to gig listings</Button>
            </Link>
          </div>
        </div>
        <div className="handle-gigs">
          <div className="reg-gig-input">
            <form onSubmit={this.handleSubmit}>
              <h3>Register a gig</h3>
              <br></br>
              <TextField
                placeholder="Event name"
                defaultValue="Event name"
                id="name"
                name="name"
                onChange={this.handleChange}
              />
              <TextField
                placeholder="Time"
                defaultValue="Time"
                type="time"
                label="Enter start time"
                id="time"
                name="time"
                InputLabelProps={{
                  shrink: true,
                }}
                inputProps={{
                  step: 300, // 5 min
                }}
                onChange={this.handleChange}
              />
              <TextField
                id="date"
                label="Select date"
                type="date"
                defaultValue="2017-05-24"
                InputLabelProps={{
                  shrink: true,
                }}
                onChange={(e) => {
                  this.setState({ date: e.target.value });
                }}
              />
              <TextField
                placeholder="Genre"
                defaultValue="Genre"
                id="genre"
                name="genre"
                onChange={this.handleChange}
              />
              <TextField
                placeholder="Tickets"
                defaultValue="Tickets"
                id="tickets"
                name="tickets"
                onChange={this.handleChange}
              />
              <TextField
                placeholder="Price"
                defaultValue="Price"
                id="price"
                name="price"
                onChange={this.handleChange}
              />
              <Button type="submit">Submit</Button>
            </form>
          </div>
          <div className="manage-gigs">
            <h3 className="manage-gig">Manage your gigs</h3>
            <br></br>
            {this.state.userDetails ? (
              <UniqueVenueListing gigList={this.state.filterGigs} />
            ) : (
              <h2>no gigs to show</h2>
            )}
          </div>
        </div>
      </div>
    );
  }
}

export default GigRegister

And here's the backend functions:

const FBAuth = (req, res, next) => {
    let idToken;
    if(req.headers.authorization && req.headers.authorization.startsWith('Bearer ')){
        idToken = req.headers.authorization.split('Bearer ')[1]
    } else {
        console.error('No token found')
        return res.status(403).json({error: 'Unauthorized'})
    }

    admin.auth().verifyIdToken(idToken)
    .then(decodedToken => {
        req.user = decodedToken;
        return db.collection('users')
        .where('userId', '==',req.user.uid)
        .limit(1)
        .get()
    })
    .then(data =>{
        req.user.venueName = data.docs[0].data().venueName;
        return next();
    })
    .catch(err => {
        console.error('Error while verifying token', err)
        return res.status(403).json(err)
    })
}


app.post('/createGigListing', FBAuth, (req,res) => {

    const newGig = {
        venueName: req.user.venueName,
        name: req.body.name,
        time: req.body.time,
        price: req.body.price,
        genre: req.body.genre,
        tickets: req.body.tickets,
        date: req.body.date,
        user:req.body.user,
        createdAt: new Date().toISOString()
    }
    db
    .collection('gig-listing')
    .add(newGig)
    .then(doc => {
        res.json({message: `document ${doc.id} created successfully`})
    })
    .catch(err =>{
        res.status(500).json({error: 'something went wrong'})
        console.error(err)
    })
})
23 hours ago

Crowdsource coding tasks.

2 Solutions


Solution

Hello @mildredthecat,

I would like to know where does the error originate from.

Does it first appear in FBAuth middleware?

If so, then the issue might be related to your server.
You'll need to set the timezone of your server to UTC (should be set somewhere in settings) since Firebase operates in UTC by default.

Let me know if this worked for you.

Thanks,
Vladimir

Hey Vladimir Thanks for your answer. The error comes when I test the /createGigListing end point in postman.
mildredthecat 8 days ago
Also, firebase doesn't appear to let me change the timezone of the server.
mildredthecat 8 days ago
Could you please provide an error log/trace from cloud functions logger? Thanks.
VladimirMikulic 8 days ago
One potential workaround would be to refresh the token once this error shows up. (passing true to getIdToken())
VladimirMikulic 7 days ago

Your new service must be in utc. The firebase server is set to utc already. Utc is the standard timezone, and all the services that you implement must be set to utc.

Why is this?

Because it's the standard time zone as I said before, and users would have different time zones, so having utc which is the base time zone is the ideal solution.

How to share/handle dates and times?

All dates and times must be shared as ISO_8601 format like these: 2020-10-12T13:15:11Z
When a user is in different time zone than utc, we send it in this format, and we save it like it is in database.

And when showing results in front end, when receiving ISO_8601 format from server, we convert it to local time zone to have it user friendly showing format.

See more: https://en.m.wikipedia.org/wiki/ISO_8601

It's ok to see times different than local time from your part in server results, mostly important is handling them in front-end.

From old experience

I have set a backend spring service that check and verify the firebase tokens and it works properly, didn't noticed any non expiring token problems. I always force the services default timezone to utc. This is the most ideal approach for data persistence either in database and for other services data sharing.

Thanks.

View Timeline