Hi All! I'm currently trying to implement a custom...
# support-questions
f
Hi All! I'm currently trying to implement a custom UI for SuperTokens. I set up API requests for sign up and sign in. However, I'm unsure how to get the onHandleEvent stuff to work with a custom UI. I'm trying to add a second step to the authentication. On successful signup using the thirdpartyemailpassword recipe, I want to redirect to another screen where I can set a username. Is there any guide to implement a custom ui without loosing features like email verification?
n
Hi, What part about onHandleEvent didnt work for you? If you are using
supertokens-auth-react
then the event firing should work out of the box
f
In my
_app.tsx
I'm doing this:
Copy code
`
if (typeof window !== 'undefined') {
  // we only want to call this init function on the frontend, so we check typeof window !== 'undefined'
  SuperTokens.init({
    appInfo: {
      // learn more about this on https://supertokens.com/docs/thirdpartyemailpassword/appinfo
      appName: process.env.NEXT_PUBLIC_SUPERTOKENS_APP_NAME as string,
      apiDomain: process.env.NEXT_PUBLIC_SUPERTOKENS_API_DOMAIN as string,
      websiteDomain: process.env.NEXT_PUBLIC_SUPERTOKENS_WEB_DOMAIN as string,
      apiBasePath: process.env.NEXT_PUBLIC_SUPERTOKENS_API_BASE_PATH,
      websiteBasePath: process.env.NEXT_PUBLIC_SUPERTOKENS_WEB_BASE_PATH,
    },
    recipeList: [
      ThirdPartyEmailPassword.init({
        signInAndUpFeature: {
          providers: [Google.init(), Facebook.init()],
        },
        getRedirectionURL: async (context) => {
          if (context.action === 'SUCCESS') {
            if (context.redirectToPath !== undefined) {
              // we are navigating back to where the user was before they authenticated
              return context.redirectToPath
            }
            return '/dashboard'
          }
          return undefined
        },
        onHandleEvent: async (context) => {
          if (context.action === 'SESSION_ALREADY_EXISTS') {
            // TODO:
          } else if (context.action === 'SUCCESS') {
            const { id, email } = context.user
            if (context.isNewUser) {
              // TODO: Sign up
              console.log('redirect to username setter')
            } else {
              // TODO: Sign in
              console.log('User signed in :D')
            }
            console.log(id, email)
          }
        },
      }),
      Session.init(),
    ],
  })
}
However there is no output in the console...
n
And can I see your UI code? The part where you are triggering SuperTokens functions etc after which you are expecting the onHandleEvent to be called?
f
Yes sure
Thats my
pages/auth/[[...path]].tsx
Copy code
import { useEffect } from 'react'
import SuperTokens from 'supertokens-auth-react'
import { redirectToAuth } from 'supertokens-auth-react/recipe/thirdpartyemailpassword'
import CustomAuthComponent from '../../components/CustomAuthComponent'
import ThirdPartyEmailPasswordAuthNoSSR from '../../components/ThirdPartyEmailPasswordAuthNoSSR'

export default function Auth() {
  // if the user visits a page that is not handled by us (like /auth/random), then we redirect them back to the auth page.

  useEffect(() => {
    if (SuperTokens.canHandleRoute() === false) {
      redirectToAuth()
    }
  }, [])
  return (
    <ThirdPartyEmailPasswordAuthNoSSR requireAuth={false}>
      <CustomAuthComponent />
    </ThirdPartyEmailPasswordAuthNoSSR>
  )
}
Thats my
<CustomAuthComponent />
``` ```
thats my
authApi
Copy code
import axios from 'axios'
import Session from 'supertokens-auth-react/recipe/session'
import { SigninForm, SignupForm } from '../schemas'

Session.addAxiosInterceptors(axios)

const API_URL =
  (process.env.NEXT_PUBLIC_SUPERTOKENS_API_DOMAIN as string) +
  (process.env.NEXT_PUBLIC_SUPERTOKENS_API_BASE_PATH as string)

const signUp = async (data: SignupForm) =>
  axios.post(
    `${API_URL}/signup`,
    {
      formFields: [
        {
          id: 'email',
          value: data.email,
        },
        {
          id: 'password',
          value: data.password,
        },
      ],
    },
    {
      headers: {
        rid: 'thirdpartyemailpassword',
        'Content-Type': 'application/json',
      },
    }
  )

const signIn = async (data: SigninForm) =>
  axios.post(
    `${API_URL}/signin`,
    {
      formFields: [
        {
          id: 'email',
          value: data.email,
        },
        {
          id: 'password',
          value: data.password,
        },
      ],
    },
    {
      headers: {
        rid: 'thirdpartyemailpassword',
        'Content-Type': 'application/json',
      },
    }
  )

const authApi = {
  signUp,
  signIn,
}

export default authApi
n
Right, what version of
supertokens-auth-react
are you using?
r
you are making APi calls youself using axios
f
yes I am
r
our SDK won't know that that's happened and it won't be able to trigger the onHandleEvent callback
f
0.22.0
r
So you should see the API call response and redirect the user yourself
f
Right, is there anyway to trigger the api using the sdk?
n
You can use the functions exported by the SDK
r
Yea. Instead of making the API calls yourself using axios, you can use the helper functions like
ThirdPartyEmailPassword.emailPasswordSignIn
etc (see https://supertokens.com/docs/auth-react/modules/recipe_thirdpartyemailpassword.html#emailPasswordSignIn-1)
f
Perfect!
Wow! I'm really amazed by not only your project but also the greatest support I've ever seen 😄
r
thank you!
n
Happy to help!
f
But I need to implement the redirection myself right?
@rp
r
Well, if you are using our SDK, it redirects to
/
be default. If you want to change it, you can see https://supertokens.com/docs/thirdpartyemailpassword/common-customizations/redirecting-post-login
actually, nvm. I am wrong. You do need to redirect yourself.
f
Okay thats what I thought. No problem tho
r
yea, with custom UI, we let you take care of all the redirections - since it's custom
f
Okay! Now I have a backend question regarding implementation of thirdparty signinup... I configured the frontend to first use the sdk to redirect to the thirdpartylogin. On successful redirection to my frontend I check if a code is present in the URL query parameters and then use the sdk to make the final request. My backend fails sometimes tho. I'm using GoLang + Fiber
@rp @nkshah2
@Adiboi
r
whats the config you have set when you do facebook.init on the backend?
f
my config
:
@rp
r
which version of the golang SDK?
If it's not the latest one, can you try again with the latest one
f
a
go get -u ./...
and a
go mod tidy
seemed to fix the issue
r
Hmm. Strange! Okay.
f
No! Actually it's a different issue...
If a session exists with the google or facebook client and i signout using the sdk and try to log back in I'm not prompted with the facebook or google ui instead I get an immediate redirect and this seems to crash the server....
r
Huh.. really. That’s odd
Can you open an issue about this on our GitHub please?
f
But only sometimes...
thats weird
also if a session exists and i allow another request through the frontend it crashes as well
r
What do you mean by allowing anothe request?
f
I mean if I logged in then don't redirect to idk e.g. dashboard and then refresh the page (so another thirdpartySigninup is triggered) the backend crashes at well.
r
Right. That’s odd. Can we get on a debug call for this? Or you can open an issue about this highlighting details of the issue
f
Ye sure I'll contact you as soon as I'm home 😄
r
thank you!
f
All right, that's odd. It had something todo with the frontend. If I implemented the thirdpartySigninup on the frontend without the react useEffect hook and without checking that a code query parameter is actually present, the backend server was crashing sometimes... It happened because sometimes the code was undefined or there were to many requests with the same code. I think there could be a part missing to handle codes that have been already verified. I'm using GoLang + Fiber with the with-fiber example located here: https://github.com/supertokens/supertokens-golang/tree/master/examples/with-fiber
r
SuperTokens has no way of knowing if a code has already been verified or not. If you give it a missing code or a code that's already verified, the API will throw an error, but it shouldn't cause a server panic. Are you getting a server panic?
f
yes this one. But this results in a server crash!
r
ohhh. Can you open an issue about this? We will fix it
f
Yes sure!
r
thanks! Will fix
f
!!! Thank you!
Okay now I'm having a different problem. I try to update the AccessTokenPayload but the changes are not reflected in the cookie... It seems that Method 1 (https://supertokens.com/docs/emailpassword/common-customizations/sessions/update-jwt-payload) doesn't work with fiber... and behaves like Method 2
r
What is the respnse of the API call in which you call
UpdateAccessTokenPayload
? Specifically, the response headers
So the new access token payload is not being sent back. Can you show me the code for how you are verifying the session, and how you are updating the payload?
and how you are sending back the response
f
sure 🙂
Copy code
func SetUsername(c *fiber.Ctx) error {
    sessionContainer := session.GetSessionFromRequestContext(c.UserContext())
    if sessionContainer == nil {
        return c.Status(500).JSON("No session found :(")
    }

    userId := sessionContainer.GetUserID()
    db := database.DB

    user := new(model.User)

    err := c.BodyParser(user)
    if err != nil {
        return c.Status(500).JSON(fiber.Map{"status": "REVIEW_INPUT", "message": "Review your input", "data": err})
    }

    user.SuperTokensID = userId

    err = db.Create(&user).Error
    if err != nil {
        return c.Status(500).JSON(fiber.Map{"status": "CANT_SET_USERNAME", "message": "Couldn't set a username for user with id " + userId})
    }

    currAccessTokenPayload := sessionContainer.GetAccessTokenPayload()
    currAccessTokenPayload["username_set"] = true

    err = sessionContainer.UpdateAccessTokenPayload(currAccessTokenPayload)
    if err != nil {
        c.Status(500).JSON(fiber.Map{"status": "UPDATE_ACCESSTOKEN_FAILED", "message": "Couldn't update the access token payload for user with id " + userId})
    }

    fmt.Println(sessionContainer.GetAccessTokenPayload())

    return c.Status(200).JSON(fiber.Map{"status": "OK", "message": "Username successfuly set."})
}
Thats my handler... and this is my middleware :
Copy code
func VerifySessionWithoutUsername(options *sessmodels.VerifySessionOptions) fiber.Handler {
    return func(c *fiber.Ctx) error {
        adaptor.HTTPHandler(session.VerifySession(options, func(w http.ResponseWriter, r *http.Request) {
            c.SetUserContext(r.Context())
        }))(c)

        if options != nil && !*options.SessionRequired {
            return c.Next()
        }

        sessionContainer := session.GetSessionFromRequestContext(c.UserContext())
        if sessionContainer != nil {
            return c.Next()
        } else {
            return c.Status(401).JSON("Please authorize yourself.")
        }
    }
}
r
Ok i think the middleware we have provided in the example app has a few mistakes in it. Will check and get back ASAP
thanks for pointing this out
f
Awesome man! Best support I ever had!
r
So, if we do this:
Copy code
go
func verifySession(options *sessmodels.VerifySessionOptions) fiber.Handler {
    return func(c *fiber.Ctx) error {
        return adaptor.HTTPHandler(session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) {
            //setting up the verified session context from http request to the fiber context
            c.SetUserContext(r.Context())
            c.Next()
        }))(c)
    }
}
then the response has the right headers for updating the access token payload. However, the response body that gets sent to the client is empty
And we want to call
c.Next()
in
VerifySession
's callback.
f
So this is the final middleware
r
not really. It still has some issues
f
oh okay but it should work for basic session verification and everything?
r
not yet. Still trying to figure it out
f
You are awesome dude!
r
hey @flixoflax Try this verifySession function:
Copy code
go
func verifySession(options *sessmodels.VerifySessionOptions) fiber.Handler {
    return func(c *fiber.Ctx) error {
        var httpResponse http.ResponseWriter
        callbackCalled := false
        adaptor.HTTPHandler(session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) {
            callbackCalled = true
            httpResponse = rw
            //setting up the verified session context from http request to the fiber context
            c.SetUserContext(r.Context())
        }))(c)

        if options != nil && !*options.SessionRequired {
            err := c.Next()
            if err != nil {
                return err
            }
        }
        if callbackCalled {
            err := c.Next()
            if err != nil {
                return err
            }
            // the API may have modified the response headers, so we get that and set
            // it in the fiber context
            for key, valueArr := range httpResponse.Header() {
                valueStr := ""
                for i := 0; i < len(valueArr); i++ {
                    valueStr += valueArr[i]
                    if i < len(valueArr)-1 {
                        valueStr += ", "
                    }
                }
                c.Set(key, valueStr)
            }
        }
        return nil
    }
}
should work. Lmk if you face any issues
f
Oh shit! Thanks man!
r
@flixoflax the function below takes care of a few more edge cases:
Copy code
go
func verifySession(options *sessmodels.VerifySessionOptions) fiber.Handler {
    return func(c *fiber.Ctx) error {
        return adaptor.HTTPHandlerFunc(http.HandlerFunc(session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) {
            c.SetUserContext(r.Context())
            err := c.Next()
            if err != nil {
                err = supertokens.ErrorHandler(err, r, rw)
                if err != nil {
                    rw.WriteHeader(500)
                    _, _ = rw.Write([]byte(err.Error()))
                }
                return
            }
            c.Response().Header.VisitAll(func(k, v []byte) {
                if string(k) == fasthttp.HeaderContentType {
                    rw.Header().Set(string(k), string(v))
                }
            })
            rw.WriteHeader(c.Response().StatusCode())
            _, _ = rw.Write(c.Response().Body())
        })))(c)
    }
}
f
Wow that’s perfect! Thanks for the fast and amazing support!
https://github.com/supertokens/supertokens-golang/issues/107 Now this issue is bugging me a bit... But my findings where a little different... It appears that the server panics when you override the originalApiImplementation and send a falsy request (e.g. formFields missing) I don't send a custom response.
Or wait maybe I do because I update the AccessTokenPayload
r
We will have a look at this sometime next weejk.
f
Great!
s
import Passwordless from 'supertokens-auth-react/recipe/passwordless'; import Session from 'supertokens-auth-react/recipe/session'; export const authConfig = () => ({ appInfo: { appName: process.env.NEXT_PUBLIC_APPNAME, apiDomain: process.env.NEXT_PUBLIC_APIDOMAIN, websiteDomain: process.env.NEXT_PUBLIC_DOMAIN, apiBasePath: process.env.NEXT_PUBLIC_APIPATH, websiteBasePath: process.env.NEXT_PUBLIC_PATH, }, recipeList: [ Passwordless.init({ useShadowDom: false, contactMethod: 'PHONE', signInUpFeature: { defaultCountry: 'IN', emailOrPhoneFormStyle: { button: { backgroundColor: 'rgba(65, 83, 240, 1)', border: '0px', width: '100%', margin: '0 auto', }, superTokensBranding: { display: 'none', }, headerTitle: { fontFamily: 'sans-serif', }, container: { height: '550px', display: 'flex', justifyContent: 'center', alignItems: 'center', }, }, }, getRedirectionURL: async (context) => { if (context.action === 'SUCCESS') { if (context.isNewUser) { // user signed up return '/wizard'; } // user signed in return '/profile'; } return undefined; }, }), Session.init(), ], });
how to override the otp entering ui ? pls help
r
Hey @sreehari_jayaraj did you checkout the react override component section?
What exactly do you want to do?
3 Views