supertokens-core icon indicating copy to clipboard operation
supertokens-core copied to clipboard

Signout API doesnt remove cookies if the user was deleted

Open frostbeer opened this issue 3 years ago • 15 comments
trafficstars

🐛 Bug Report

User signs in to supertokens with email provider google on a browser. Then the user is deleted by using the deleteUser in the SDK, this is done without a browser in the middle. User tries to signout from the browser and the signout API returns 200 but without removing the cookies like sAccessToken that is still valid and not expired. Because of that, the browser thinks the user is still signed in.

Useful informations

Stack

  • The Core runs inside in a docker using the image: imageRepository: supertokens/supertokens-postgresql imageRegistery: registry.supertokens.io imageTag: 3.14
  • SDK in the backend is golang v0.7.2
  • Browser uses supertokens react (but this issue also happened to me when using postman) [email protected] [email protected]

Steps

  1. User signs in with email provider google, cookies are created and saved in the browser.
  2. Delete the user from supertokens by calling in the backend supertokens.DeleteUser(userId)
  3. User in the browser calls signout, backend returns 200 without deleting the cookies in the response headers.

frostbeer avatar Sep 15 '22 07:09 frostbeer

Thanks for raising this. We will provide a fix for this asap.

rishabhpoddar avatar Sep 15 '22 08:09 rishabhpoddar

In the meantime, you can override the backend sign out function in this way:

import (
	"github.com/supertokens/supertokens-golang/recipe/session"
	sesserrors "github.com/supertokens/supertokens-golang/recipe/session/errors"
	"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
	"github.com/supertokens/supertokens-golang/supertokens"
)

func main() {
	session.Init(&sessmodels.TypeInput{
		Override: &sessmodels.OverrideStruct{
			APIs: func(originalImplementation sessmodels.APIInterface) sessmodels.APIInterface {
				ogSignOut := *originalImplementation.SignOutPOST
				(*originalImplementation.SignOutPOST) = func(options sessmodels.APIOptions, userContext supertokens.UserContext) (sessmodels.SignOutPOSTResponse, error) {
					_, err := ogSignOut(options, userContext)
					if err != nil {
						return sessmodels.SignOutPOSTResponse{}, err
					}
					clearCookies := true
					return sessmodels.SignOutPOSTResponse{}, sesserrors.UnauthorizedError{
						Msg:          "Should force cookie removal",
						ClearCookies: &clearCookies,
					}
				}
				return originalImplementation
			},
		},
	})
}

rishabhpoddar avatar Sep 15 '22 09:09 rishabhpoddar

@rishabhpoddar thanks for the solution i will try it. If im understanding it correctly every signout no matter what will return 401 and clear the cookies? returning 401 is ok? it something the client sdk will be able to handle?

frostbeer avatar Sep 15 '22 09:09 frostbeer

Yea, it should work fine.

rishabhpoddar avatar Sep 15 '22 09:09 rishabhpoddar

Hello @frostbeer ,

I tried replicating the issue using the same versions and I followed the same steps as mentioned:

  1. Login with google from the browser
  2. Call an internal API to delete the user (using postman)
  3. Try the signout from the browser and verified that the cookies are indeed getting removed.

Golang SDK version 0.7.2 and auth-react version 0.24. Also used the same core image for testing.

Can you give us some more information about your setup? Like which framework, what does your supertokens.Init code look like?

sattvikc avatar Sep 19 '22 06:09 sattvikc

Hi @sattvikc, So the sAccessToken and the other server cookies are set to expired from the signout api response in your test?

Im using gin framework and i think that is the only thing i missed. For the supertokens init i guess its quite a big code now since i override a bunch of stuff now but this is it

	err := supertokens.Init(supertokens.TypeInput{
		OnSuperTokensAPIError: handleSuperTokensError,
		Supertokens: &supertokens.ConnectionInfo{
			ConnectionURI: config.SuperTokensCoreURI,
			APIKey:        config.SuperTokensCoreAPIKey,
		},
		AppInfo: supertokens.AppInfo{
			AppName:         "the-piper",
			APIDomain:       config.PiperAuthBackendDomain,
			WebsiteDomain:   config.PiperAuthWebDomain,
			APIBasePath:     &apiBasePath,
			WebsiteBasePath: &websiteBasePath,
		},
		RecipeList: []supertokens.Recipe{
			thirdpartypasswordless.Init(tplmodels.TypeInput{
				FlowType:           "USER_INPUT_CODE",
				ContactMethodEmail: plessmodels.ContactMethodEmailConfig{Enabled: true},
				EmailDelivery:      emailOverrides.CustomMailerDeliver(),
				Providers: []tpmodels.TypeProvider{
					thirdparty.Google(tpmodels.GoogleConfig{
						ClientID:     config.GoogleOAuthClientId,
						ClientSecret: config.GoogleOAuthClientSecret,
					}),
					thirdparty.Apple(tpmodels.AppleConfig{
						ClientID: config.AppleOAuthClientId,
						ClientSecret: tpmodels.AppleClientSecret{
							KeyId:      config.AppleOAuthKeyId,
							PrivateKey: config.GetApplePrivateKey(),
							TeamId:     config.AppleOAuthTeamId,
						},
					}),
					thirdparty.Facebook(tpmodels.FacebookConfig{
						ClientID:     config.FacebookOAuthAppId,
						ClientSecret: config.FacebookOAuthAppSecret,
					}),
				},
				Override: &tplmodels.OverrideStruct{APIs: apiOverrides.OverrideAPI},
			}),
			session.Init(&sessmodels.TypeInput{
				CookieDomain: &cookieDomain,
				CookieSecure: &cookieSecure,
				Jwt: &sessmodels.JWTInputConfig{
					Enable: true,
				},
				Override: sessionOverrides.OverriddenImpl(),
			}), // initializes session features
		},
	})

I currently override the functions ConsumeCodePOST, ThirdPartySignInUpPOST. EmailSend, GetSession, CreateJWT and now SignOutPost based on what i was suggested here.

In the GetSession I just check if there is a custom header in the request and if not then it calls and returns the original implementation.

frostbeer avatar Sep 20 '22 07:09 frostbeer

Sounds good, let me check and get back.

sattvikc avatar Sep 20 '22 07:09 sattvikc

Looks like I still can't reproduce your issue. Could you help me with these values - APIDomain, WebsiteDomain, CookieDomain, CookeSecure?

Also, if you could help me with the debug logs for your scenario, it could give some pointers too. You need to set SUPERTOKENS_DEBUG=true in the env to enable the debug logs.

sattvikc avatar Sep 20 '22 07:09 sattvikc

  • api domain and website domain sits on different url but the same subdomain like auth.my.domain.com and auth-web.my.domain.com
  • cookie domain is my.domain.com
  • cookie secure for this specific case is false but it was still on https

I will try to get the logs as fast as i can

frostbeer avatar Sep 20 '22 08:09 frostbeer

This is the logs i see when calling signout

com.supertokens {t: "2022-09-20T09:48:20Z", message: "middleware: Started", file: "/go/pkg/mod/github.com/supertokens/[email protected]/supertokens/main.go:31" sdkVer: "0.7.2"}
com.supertokens {t: "2022-09-20T09:48:20Z", message: "middleware: requestRID is: session", file: "/usr/local/go/src/net/http/server.go:2084" sdkVer: "0.7.2"}
com.supertokens {t: "2022-09-20T09:48:20Z", message: "middleware: Checking recipe ID for match: thirdpartypasswordless", file: "/usr/local/go/src/net/http/server.go:2084" sdkVer: "0.7.2"}
com.supertokens {t: "2022-09-20T09:48:20Z", message: "middleware: Checking recipe ID for match: session", file: "/usr/local/go/src/net/http/server.go:2084" sdkVer: "0.7.2"}
com.supertokens {t: "2022-09-20T09:48:20Z", message: "middleware: Matched with recipe ID: session", file: "/usr/local/go/src/net/http/server.go:2084" sdkVer: "0.7.2"}
com.supertokens {t: "2022-09-20T09:48:20Z", message: "middleware: Request being handled by recipe. ID is: /signout", file: "/usr/local/go/src/net/http/server.go:2084" sdkVer: "0.7.2"}
com.supertokens {t: "2022-09-20T09:48:20Z", message: "getSession: Started", file: "/go/pkg/mod/github.com/supertokens/[email protected]/recipe/session/sessionwithjwt/recipeImplementation.go:69" sdkVer: "0.7.2"}
com.supertokens {t: "2022-09-20T09:48:20Z", message: "getSession: rid in header: true", file: "/go/pkg/mod/github.com/supertokens/[email protected]/recipe/session/sessionwithjwt/recipeImplementation.go:69" sdkVer: "0.7.2"}
com.supertokens {t: "2022-09-20T09:48:20Z", message: "getSession: request method: POST", file: "/go/pkg/mod/github.com/supertokens/[email protected]/recipe/session/sessionwithjwt/recipeImplementation.go:69" sdkVer: "0.7.2"}
com.supertokens {t: "2022-09-20T09:48:20Z", message: "getSession: Value of doAntiCsrfCheck is: true", file: "/go/pkg/mod/github.com/supertokens/[email protected]/recipe/session/sessionwithjwt/recipeImplementation.go:69" sdkVer: "0.7.2"}
com.supertokens {t: "2022-09-20T09:48:20Z", message: "getSession: Success!", file: "/go/pkg/mod/github.com/supertokens/[email protected]/recipe/session/sessionwithjwt/recipeImplementation.go:69" sdkVer: "0.7.2"}
com.supertokens {t: "2022-09-20T09:48:20Z", message: "Sending response to client with status code: 200", file: "/go/pkg/mod/github.com/supertokens/[email protected]/recipe/session/api/signout.go:34" sdkVer: "0.7.2"}
com.supertokens {t: "2022-09-20T09:48:20Z", message: "middleware: Ended", file: "/usr/local/go/src/net/http/server.go:2084" sdkVer: "0.7.2"}

let me know if you want me to provide more.

frostbeer avatar Sep 20 '22 09:09 frostbeer

Thank you, I'll do my investigations and get back on this.

sattvikc avatar Sep 20 '22 09:09 sattvikc

if it matters it was with a passwordless email user and not with google thirdparty.

i guess i can also try to do a clean version without any overrides on my end and see if its working.

frostbeer avatar Sep 20 '22 10:09 frostbeer

Yea sure, I was testing it with google user. thanks for the additional info. I'll get back once I find something.

sattvikc avatar Sep 20 '22 10:09 sattvikc

@frostbeer I tried to setup very similar setup as you mentioned. The cookies are being set to expire. I think you missed antiCSRF setting in the init you shared as well, I have tried with and without that too.

Can you share the GetSession override code and confirm that you haven't overridden RevokeSession? Also please share exactly how you have overridden the GetSession.

sattvikc avatar Sep 21 '22 10:09 sattvikc

Didnt override RevokeSession method.

GetSession override code is this

type OverrideSuperTokensSession struct {}

func(override OverrideSuperTokensSession) OverriddenImpl() *sessmodels.OverrideStruct {
	return &sessmodels.OverrideStruct{
		OpenIdFeature: &openidmodels.OverrideStruct {
			JwtFeature: &jwtmodels.OverrideStruct {
				Functions: override.OverriddenJWT,
			},
		},
		Functions: override.OverrideSession,
		APIs: override.OverrideAPI,
	}
}

func(override OverrideSuperTokensSession) OverrideSession(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {
	*originalImplementation.GetSession = override.overrideGetSession(originalImplementation)
	return originalImplementation
}

func(override OverrideSuperTokensSession) overrideGetSession(originalImplementation sessmodels.RecipeInterface) func(
	req *http.Request, res http.ResponseWriter, options *sessmodels.VerifySessionOptions,
	userContext supertokens.UserContext) (*sessmodels.SessionContainer, error) {

	originalGetSession := *originalImplementation.GetSession

	return func(req *http.Request, res http.ResponseWriter, options *sessmodels.VerifySessionOptions, userContext supertokens.UserContext)(*sessmodels.SessionContainer, error) {
		jwtToken := jwt.GetBearerTokenFromAuthorization(req.Header)

		if jwt.IsTokenJWT(jwtToken) {

			log.Info("Trying to get session from JWT token")
			jwtSession, err := override.getSessionContainerFromJWT(jwtToken)

			if err != nil {
				log.Errorf("Failed to get session from the JWT token %s", jwtToken)
				return nil, err
			} else {
				return jwtSession, nil
			}
		} else {
			return originalGetSession(req, res, options, userContext)
		}
	}
}

then overriding the Session in the init like so

session.Init(&sessmodels.TypeInput{
				CookieDomain: &cookieDomain,
				CookieSecure: &cookieSecure,
				Jwt: &sessmodels.JWTInputConfig{
					Enable: true,
				},
				Override: sessionOverrides.OverriddenImpl(),
			})

im pretty sure it calls the original get session in my override since there is no authorization bearer in those requests but i can try and make sure.

I appreciate all your work what is the anti CSRF init i missed can you point me in the docs to the example? i just started from the backend setup https://supertokens.com/docs/thirdparty/quick-setup/backend

frostbeer avatar Sep 21 '22 11:09 frostbeer

Thank you for sharing the details, will get back once I have something further.

sattvikc avatar Sep 22 '22 05:09 sattvikc

@frostbeer I tried with your overrides, signout just works as expected. Might be something to do with the header as you mentioned. Else, could you try with the latest version of the SDK?

sattvikc avatar Sep 23 '22 05:09 sattvikc

Closing this issue since we can't reproduce it. But feel free to reopen it.

rishabhpoddar avatar Sep 29 '22 17:09 rishabhpoddar