https://supertokens.com/ logo
Title
s

segidev

10/18/2022, 11:44 AM
Did anyone experience the issue that randomly people that login suddenly end up as another user? We had this case now multiple times very randomly. Of course the users can't really tell what they did but one simply logged in yesterday and ended up being someone else. We use the method
EmailPassword.signIn
with no logic in between or anything hooking in.
n

nkshah2

10/18/2022, 12:01 PM
Hi @segidev , Could you elaborate a little on the issue you were facing? (Examples and code for your setup would help)
s

segidev

10/18/2022, 12:02 PM
Sure, this is the code we have in the Frontend:
javascript
async login(payload: { email: string; password: string }) {
  const response = await EmailPassword.signIn({
    formFields: [
      {
        id: "email",
        value: payload.email,
      },
      {
        id: "password",
        value: payload.password,
      },
    ],
  });

  if (response.status === "OK") {
    const verification = await EmailVerification.isEmailVerified();

    if (!verification.isVerified) {
      await EmailVerification.sendVerificationEmail();
      throw Error("emailverification");
    } else {
      await this.verifyAuth();
    }
    return;
  }
  throw Error(response.status);
}
Initialized it is with that:
javascript
SuperTokens.init({
  appInfo: {
    appName: "ono",
    apiDomain: import.meta.env.VITE_API_HOST,
    apiBasePath: "/auth",
  },
  recipeList: [EmailVerification.init(), EmailPassword.init(), Session.init()],
});
The user was not logged in (at least that's what he told) he then logged in and saw that the account he was using was from another user
n

nkshah2

10/18/2022, 12:03 PM
And how are you identifying users?
s

segidev

10/18/2022, 12:05 PM
We send a GET request to the /user route (which contains the cookies then) and use the session to receive the user from the database
go
func (c *UserController) getUser(ctx *gin.Context) {
    session := session.GetSessionFromRequestContext(ctx.Request.Context())
    userID := uuid.MustParse(session.GetUserID())

    // Request the full user
    u, err := c.getFullUser(userID)
    if err != nil {
        NewError(ctx, err)
        return
    }

    ctx.JSON(http.StatusOK, u)
}
Using
"github.com/supertokens/supertokens-golang/recipe/session"
n

nkshah2

10/18/2022, 12:06 PM
What does
getFullUser(userID)
do?
s

segidev

10/18/2022, 12:06 PM
The route protection (using Gin) is done with
go
func OnlyAuthorized(options *sessmodels.VerifySessionOptions) gin.HandlerFunc {
    return func(c *gin.Context) {
        session.VerifySession(
            options,
            func(rw http.ResponseWriter, r *http.Request) {
                c.Request = c.Request.WithContext(r.Context())
                c.Next()
            },
        )(c.Writer, c.Request)
        // We call Abort so that the next handler in the chain is not called, unless we call Next explicitly
        c.Abort()
    }
}
The
getFullUser
does this:
go
func (c *UserController) getFullUser(userID uuid.UUID) (*models.User, error) {
    u, err := c.dataService.DB.User.Query().Where(user.ID(userID)).WithProfile().Only(context.Background())
    if err != nil {
        return nil, err
    }
    response := models.User{
        ID:    u.ID.String(),
        EMail: u.Email,
    }
    if u.Edges.Profile != nil {
        response.Profile = &models.UserProfileResponse{
            BaseViewModel: models.BaseViewModel{
                ID:         u.Edges.Profile.ID,
                CreateTime: u.Edges.Profile.CreateTime.UTC().Format(time.RFC3339),
            },
            UserProfileRequest: models.UserProfileRequest{
                FirstName: u.Edges.Profile.FirstName,
                LastName:  u.Edges.Profile.LastName,
            },
        }
    }
    return &response, nil
}
Could there be a relation to users that were logged in once, but then the account was deleted but they still have an old refresh token or something? But still then it doesn't make sense why they end up being another user
n

nkshah2

10/18/2022, 12:10 PM
How are you populating users in your DB after sign up? Also to answer your question users who's accounts were deleted would get an unauthorised response, they still would not be treated as a different user
s

segidev

10/18/2022, 12:11 PM
The signup is simply:
ts
async register(payload: { email: string; password: string }) {
  const response = await EmailPassword.signUp({
    formFields: [
      {
        id: "email",
        value: payload.email,
      },
      {
        id: "password",
        value: payload.password,
      },
    ],
  });

  if (response.status === "OK") {
    await EmailVerification.sendVerificationEmail();
    return;
  }
  throw Error(response.status);
}
n

nkshah2

10/18/2022, 12:12 PM
I meant on your backend
s

segidev

10/18/2022, 12:12 PM
There is nothing done in the API further
We use the SuperTokens with GIN which exposes those routes
We have no control over these routes, or let's say we do not interfer with them
n

nkshah2

10/18/2022, 12:13 PM
So
c.dataService.DB.User.Query().Where(user.ID(userID))
is not your custom own database then?
s

segidev

10/18/2022, 12:13 PM
The only thing we have is this to connect ST to Gin
go
// Adding the SuperTokens middleware
r.Use(func(c *gin.Context) {
    supertokens.Middleware(
        http.HandlerFunc(
            func(rw http.ResponseWriter, r *http.Request) {
                c.Next()
            },
        ),
    ).ServeHTTP(c.Writer, c.Request)
    c.Abort()
})
Currently we are using ent framework. We have "faked" the ent model to be able to use the supertokens generated table
But that is just a query builder
And the query will query the
emailpassword_users
table
go
type User struct {
    ent.Schema
}

func (User) Annotations() []schema.Annotation {
    return []schema.Annotation{
        entsql.Annotation{Table: "emailpassword_users"},
    }
}

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("id").
            Unique().
            GoType(uuid.New()).
            Immutable().
            StorageKey("user_id"),
        field.String("email").Unique().Immutable(),
        field.String("password_hash").Unique().Sensitive().Immutable(),
        field.Int("time_joined").Immutable(),
    }
}

func (User) Edges() []ent.Edge {
    return []ent.Edge{
        edge.To("profile", UserProfile.Type).
            Unique(),
}
n

nkshah2

10/18/2022, 12:17 PM
So im a little unclear on a few things here: - You use EmailPassword.signin/signUp on the frontend which is correct - On the backend you dont seem to be doing anything post sign up so im not sure how you are storing profile information of a user - Your logic to fetch user information seems fully custom as well (the backend SDK has some helper function for this but you dont seem to be using them)
s

segidev

10/18/2022, 12:17 PM
The profile is not relevant
It's not used yet
n

nkshah2

10/18/2022, 12:18 PM
So how does the user identify whether or not they ended up being logged in as someone else?
s

segidev

10/18/2022, 12:18 PM
We have the getFullUser to be able to fetch the Profile along with what Supertokens provides
Because the email that comes back from the query to the
emailpassword_users
table is wrong. The whole session was for another user and therefore all the data of that user could be seen
It feels like a wrong access or refresh token was given
That's my only explanation
Since we always use
session.GetSessionFromRequestContext
to retreive the current user
n

nkshah2

10/18/2022, 12:22 PM
So I would first recommend using some of the helper functions to fetch user information from SuperTokens instead of querying the database yourself. For example using https://supertokens.com/docs/emailpassword/common-customizations/get-user-info#using-getuserbyid instead of querying the
emailpassword_users
table directly
Users would never get incorrect tokens in the responses, the only thing I can think of is that they logged in as a different user at some point (incognito, different browser etc) and then did not realise when they switched between them making it appear as if its the wrong account This is assuming ofcourse you dont have any custom logic for interacting directly with the SuperTokens databases. To rule this out, can you post the logic for where you call SuperTokens init on the backend?
s

segidev

10/18/2022, 12:30 PM
Sure i have to upload a file cause the message is too long
n

nkshah2

10/18/2022, 12:30 PM
Sure
s

segidev

10/18/2022, 12:30 PM
But the user never was logged in as the user who he ended up
He is an external user that should never have access to that account
n

nkshah2

10/18/2022, 12:34 PM
That looks right, what does
this.verifyAuth()
on the frontend do?
s

segidev

10/18/2022, 12:38 PM
ts
async verifyAuth() {
  if (await Session.doesSessionExist()) {
    if (!(await EmailVerification.isEmailVerified()).isVerified) {
      return false;
    }
    try {
      const resp = await axiosInstance.get<SuperTokenUserViewModel>(
        "/api/v1/user"
      );
      this.setUser(resp.data);
      return true;
    } catch (error) {
      if (axios.isAxiosError(error)) {
        if (error.code === "ERR_NETWORK") {
          this.setGlobalToast({
            detail: i18n.global.t(trans.NETWORK_PROBLEM_PLEASE_TRY_AGAIN),
            life: 3000,
            severity: "error",
          });
        }
      }
      return false;
    }
  }
  return false;
}
r

rp

10/18/2022, 12:54 PM
have you been able to replicate this issue at all? Cause otherwise it's really hard to debug what's going on
s

segidev

10/18/2022, 12:58 PM
Not on my side
But it happened already 3 times
So it is an existing issue
But totally not reproducible
r

rp

10/18/2022, 1:03 PM
hmm. This is a tough one
do you have any recordings for what the user did?
s

segidev

10/18/2022, 1:07 PM
Sadly not but i will ask if he can reproduce it
And if then have a session where he can show it so i can retreive the current cookies, browser etc
Thank you so far. I will come back if i have news
r

rp

10/18/2022, 1:28 PM
sounds good. Thanks
s

segidev

10/19/2022, 5:25 AM
@rp So the client said he was logged in and did a simple browser refresh, then was the other user.
Could it be that due to a database migration somehow the
session_info
table is wrongly mixed up with the
emailpassword_users
?
What would be the proper way to clear all sessions. By truncating the
session_info
table?
n

nkshah2

10/19/2022, 5:27 AM
@kakashi_44 Can help here
k

kakashi_44

10/19/2022, 5:37 AM
Hey @segidev , truncating
session_info
table should be good enough if you want to remove all the sessions
s

segidev

10/19/2022, 5:38 AM
Ok thank you 👍