diff --git a/content/express-session-vs-supertokens-for-handling-user-sessions/image243x-p-800.png b/content/express-session-vs-supertokens-for-handling-user-sessions/image243x-p-800.png new file mode 100755 index 00000000..88791c9f Binary files /dev/null and b/content/express-session-vs-supertokens-for-handling-user-sessions/image243x-p-800.png differ diff --git a/content/express-session-vs-supertokens-for-handling-user-sessions/image253x-p-800.png b/content/express-session-vs-supertokens-for-handling-user-sessions/image253x-p-800.png new file mode 100755 index 00000000..bc49a339 Binary files /dev/null and b/content/express-session-vs-supertokens-for-handling-user-sessions/image253x-p-800.png differ diff --git a/content/express-session-vs-supertokens-for-handling-user-sessions/image263x-p-800.png b/content/express-session-vs-supertokens-for-handling-user-sessions/image263x-p-800.png new file mode 100755 index 00000000..372700b1 Binary files /dev/null and b/content/express-session-vs-supertokens-for-handling-user-sessions/image263x-p-800.png differ diff --git a/content/express-session-vs-supertokens-for-handling-user-sessions/image273x-p-1080.png b/content/express-session-vs-supertokens-for-handling-user-sessions/image273x-p-1080.png new file mode 100755 index 00000000..b1f442a7 Binary files /dev/null and b/content/express-session-vs-supertokens-for-handling-user-sessions/image273x-p-1080.png differ diff --git a/content/express-session-vs-supertokens-for-handling-user-sessions/image283x.png b/content/express-session-vs-supertokens-for-handling-user-sessions/image283x.png new file mode 100755 index 00000000..82793a0d Binary files /dev/null and b/content/express-session-vs-supertokens-for-handling-user-sessions/image283x.png differ diff --git a/content/express-session-vs-supertokens-for-handling-user-sessions/image293x-p-800.png b/content/express-session-vs-supertokens-for-handling-user-sessions/image293x-p-800.png new file mode 100755 index 00000000..95b41541 Binary files /dev/null and b/content/express-session-vs-supertokens-for-handling-user-sessions/image293x-p-800.png differ diff --git a/content/express-session-vs-supertokens-for-handling-user-sessions/index.md b/content/express-session-vs-supertokens-for-handling-user-sessions/index.md new file mode 100644 index 00000000..5b70e200 --- /dev/null +++ b/content/express-session-vs-supertokens-for-handling-user-sessions/index.md @@ -0,0 +1,210 @@ +--- +title: Express-session vs SuperTokens for handling user sessions +date: "2020-06-11" +description: "This article will be comparing SuperTokens to Node’s most popular session management library– express-session. Learn more about the comparison based on different security and performance metrics." +cover: "express-session-vs-supertokens-for-handling-user-sessions.png" +category: "sessions" +author: "Advait Ruia" +--- + +This article will be comparing SuperTokens to Node’s most popular session management library – [express-session](https://www.npmjs.com/package/express-session). The comparison will be done using a point system, where a point will be awarded to a library’s score if it performs well in a given metric. Here are the metrics we will be using: + +- [**Security**](#security): This is especially relevant because we’re talking about user session management. +- [**Scalability**](#scalability): Time and space costs. This is relevant because most API calls require session authentication. +- [**Reliability and Correctness**](#reliability-and-correctness): It is imperative that the library takes care of technical issues like keeping its state consistent despite network or server failures and taking care of synchronising its logic in a clustered environment. +- [**App User experience**](#app-user-experience): We want to use a library that can provide the right experience for your app users – for example, does the library enable a user to be logged in for weeks or months whilst also providing good security? +- [**Time to production**](#time-to-production): We will look at factors such as time to integrate the library into an app, available support, and ease of understanding of the library code. +- [**Maintenance cost**](#maintenance-costs): We will assess costs for runtime (RAM and processing power) and internal and external monetary costs. + + +## Security + +![lady holding blue folder](./image243x-p-800.png) + +We explore how both libraries mitigate against different session attacks below. For a background on why session security is important, read our [other blog post](/blog/all-you-need-to-know-about-user-session-security?s=se/). The attack vectors for sessions are: the frontend, over the network and the backend. + + +## Token theft via XSS: + +In an XSS attack, an attacker can maliciously inject JavaScript into a website on the victim’s browser. The injected code reads and transmits session tokens to the attacker. + +Exclusively using HttpOnly cookies to store auth tokens disallows any JavaScript running on the browser from reading them, preventing token theft via XSS. Both SuperTokens and express-session protect against this attack. + +**SuperTokens: 1, Express-sessions: 1** + + +## Brute force + +This attack can be solved easily by using long length tokens that have a high amount of entropy. Both SuperTokens and express-session use long length tokens, mitigating this attack. + +**SuperTokens: 2, Express-sessions: 2** + + +## Token theft via Man in the middle attack: + +While this attack is mostly preventable using HTTPS, it can still be successfully executed – especially in corporate environments (Check out [this](https://mitmproxy.org/) tool) where access to the internet is controlled via an HTTPS proxy. As such, there is no full proof method to prevent this attack and we can only attempt to minimize the damage. + +Express-session uses just one long lived session token. Hence, for each API request, the frontend needs to send this token to the backend for authentication purposes. If a MITM attack is being carried out, any request the app makes will expose this critical token to the attacker who can then use it to gain access to the victim’s account – potentially for months. + +SuperTokens uses two auth tokens: one short lived access token, and one long lived refresh token. The recommended lifespan of the access token is <= 1 hour. For most API calls, only the access token is sent over the network. This means, a MITM attacker will likely only get hold of the access token, which would grant them access to the victim’s account for a limited amount of time. SuperTokens is also able to detect token theft in the event that the attacker gets hold of the refresh token. + +It’s clear that SuperTokens minimises damage during this attack. + +**SuperTokens: 3, Express-session: 2** + +## Session fixation: + +The attack consists of inducing a user to authenticate themselves with a known session ID, and then hijacking the user-validated session by the knowledge of the used session ID. + +Session fixation can be prevented by changing the auth tokens upon successful user login. +While express-session provides the regenerate function, it’s the developer’s responsibility to call this and make sure any session data is carried forward. Since many developers use passport JS (which doesn’t call this function – see [here](https://github.com/jaredhanson/passport/issues/192)) for login purposes, this attack vector goes unresolved. + +In the case of SuperTokens, the only way to associate a userId to a session is by creating a new session. This means that the auth tokens change too, mitigating this attack. + +SuperTokens wins a point. + +**SuperTokens: 4, Express-session: 2** + +## Secret key compromise: + +In the context of express-session, a secret key is used to sign the cookies. This signature helps the backend ensure that any incoming session was in fact generated by it (though I’d argue that the same can be achieved by simply having a longer sessionID, even without a signature). These session IDs (SIDs) in the signed cookies are not guessable, so if the attacker is only able to steal the secret key, then there is not much they can do, but if the attacker also gets hold of SIDs from the storage layer (which is likely since they managed to get the secret somehow), then they can hijack the sessions associated with those SIDs quite easily. + +SuperTokens also has a secret key. This is used to create a [JWT access token](/blog/are-you-using-jwts-for-user-sessions-in-the-correct-way) that is sent to the client. Here the secret key is necessary and used to ensure integrity of any incoming token. If the key is compromised, then the attacker would be able to change any JWT and impersonate any user in the system (as long as they know that user’s user ID). + +The only way to minimise the impact of a stolen key, is to keep changing it: + +- Express-session, allows developers to specify an array of secret keys. Tokens are always signed with the latest key, but are verified against all of the keys in the array until one of the keys work. Let’s say the array is *[A, B, C]*, and the attacker manages to get the key *C*. They can now maliciously use this key to sign tokens. Since the tokens are verified against all the keys, even if there were a new key in the system, say, *D* (the array now is *[A, B, C, D]*), the attacker can continue to use *C* and act maliciously. The only solution to this problem is to stop using *C*. However, since express-session uses long lived sessions (weeks or even months), during which the SID does not change, revoking *C* from the array would cause all users whose SIDs are signed with *C* to be logged out! + +- Since SuperTokens uses two auth tokens out of which only one is a JWT, the library can go about revoking the stolen key and replacing it with a new one without causing any user logouts *[1](#footnote)*. In fact, the library does JWT signing key rotation for you automatically. + + +## Data theft from database: + +Express-session stores all session IDs in plain text. This means that if an attacker was to get hold of this information (and assuming that they also got hold of the secret key – which is quite probable if they have access to the db), they could easily hijack the session of all currently logged in users. + +SuperTokens stores only the hashed version of the session tokens. This means that if anyone (even application developers) gets a hold of them and its corresponding session data, they will not be able to use it to hijack any user’s session. + +SuperTokens clearly wins this point + +**SuperTokens: 6, Express-session: 3** + +## CSRF (Cross-site Request Forgery): + +Express-session does nothing to prevent this. On the other hand, SuperTokens has CSRF protection which is enabled by default for all non-GET APIs. By simply passing a boolean value to the function that does session verification, one can enable / disable CSRF protection for different APIs. + + +## Session hijacking: + +There are two aspects to this attack: + +- Preventing unauthorised access via token theft: In theory, it’s impossible to prevent this since the session tokens are being sent to an untrusted device (the app’s frontend). We can only minimise the probability of this event. The most prominent method to do so is to use frequently changing session tokens. + +- Detecting token theft and taking appropriate action: Traditional methods of theft detection include methods like analysing IP address or device fingerprint changes. However, these methods lead to many false negatives and positives. The better way to do this is to use the concept of rotating refresh tokens as suggested by IETF in their [OAuth RFC](https://datatracker.ietf.org/doc/html/rfc6749#section-10.4). After detection, the session solution should allow for immediate or quick revocation of the affected session. + +SuperTokens follows both these practices. It uses two tokens (access and refresh tokens), which keep changing frequently, and also has token theft detection using the [IETF recommended method.](https://datatracker.ietf.org/doc/html/rfc6819#section-5.2.2.3) Below is the session flow that shows the usage of the tokens: + +![three images explaining auth flow](./image253x-p-800.png) + +Meanwhile, express-session uses one long lived token and has no means to detect token theft. Below is the session flow for express-session: + +![two images explaining auth flow](./image263x-p-800.png) + +SuperTokens wins this point as well. + +**SuperTokens: 8, Express-session: 3** + + +## Scalability + +![two men discussing](./image273x-p-1080.png) + +Session authentication is performed in most APIs. Hence, it’s necessary that the methodology for verifying a session is as efficient as possible in terms of time complexity. When talking about latency, it is important to realise that I/O based operations, like reading from a database, are the bottlenecks. + +**Time complexity:** Express-session does a database call for each verification of a session, and also provides no caching functionality out of the box (unless using redis storage). Since SuperTokens uses a JWT as its access token, most calls to verify a session do not require any network operation. SuperTokens gain’s a point here. + +**SuperTokens: 9, Express-session: 3** + +## Reliability and Correctness + +Correctness means that the code works as expected in normal and edge case situations. Based on our evaluation, we found that express-session is not thread safe. The specific failure case is that it is possible to bring back a revoked user session in certain scenarios: + +1. A user session already exists for user1 +2. Let’s say we have two requests (A and B) sent at the same time from user1. +3. Request A, reaches the server first and gets the session object in its API. +4. After that, request B reaches the server and revokes this session. +5. Request A then (using its already obtained session object in step 3), modifies some session data and then saves that. +5. This results in the revoked session being alive again. + +For SuperTokens, we’ve made sure that once a session is removed from the database, it never comes back. We do this by enforcing that all update operations never insert a row in case the primary field (session ID) doesn’t exist.. + +**SuperTokens: 10, Express-session: 3** + + +## App User experience + +In the context of sessions, a good user experience means that we enable long lived sessions so that app users do not have to keep logging in repeatedly. Both Express-session and SuperTokens enable this, so it’s a tie. + +**SuperTokens: 11, Express-session: 4** + +## Time to production + +![man working on his laptop](./image283x.png) + +**Completeness:** Session management is tightly tied to user management. This means, given a session, one should be able to retrieve the associated user information, and given a user ID, one should be able to retrieve all the associated sessions. While express-session allows you to get user information given a session ID, going the other way around would require customisations. On the other hand, SuperTokens have both way bindings. + +**SuperTokens: 12, Express-session: 4** + +**Complexity in initial setup:** SuperTokens is more complex to set up than Express-session is, albeit for good reason – it adds more security and it also has more features. This point goes to express-session. + +**SuperTokens: 12, Express-session: 5** + +**Community support:** Since Express-Session is an older library and is being used by a lot of developers, it has great support and a big presence on sites such as StackOverflow. SuperTokens does not yet have equally good community support. Express-Session wins a point here. + +**SuperTokens: 12, Express-session: 6** + +**Dedicated support:** For many industries like banking and healthcare, having dedicated support for third party tools being used for critical applications is compulsory. Unlike Express-session, SuperTokens provides dedicated support and hence gains a point. + +**SuperTokens: 13, Express-session: 6** + + +## Maintenance costs + +![a man and a woman trying to drill a computer](./image293x-p-800.png) + +**Runtime cost:** Higher costs are incurred by higher processor and RAM usage. Express-session, being a library for Node, doesn’t require extra resources to run in terms of processors or RAM. Meanwhile, SuperTokens is run as a separate process. Due to this, it requires slightly more RAM and processing than express-session does. Hence, express-session gets a point here. + +**SuperTokens: 13, Express-session: 7** + +**External costs:** Express-session is completely free to use. SuperTokens has a community (free) and a paid version. Some of the features discussed above are only available in the paid version, so it’s only fair to give a point to express-session for this metric. + +**SuperTokens: 13, Express-session: 8** + +**Internal Costs:** Express-session is a relatively simple library, which means that as apps scale, eventually, more and more work needs to be put into “scaling” / changing the session management system. Examples of changes are: + +- Building infrastructure to be able to monitor / revoke sessions in real time from a dashboard. +- Quite often, the API layer is built using multiple frameworks (like NodeJS). Hence, a cost here is to build a library for another backend framework that is compatible with Express-sessions. +- Adding a caching layer to session to improve performance. +- Adding more functionality to the core session logic. For example adding a feature to revoke sessions based on changes in device fingerprints. +- Adapting to changes in the ecosystem, especially frontend technologies. For example, to integrate express-session with a react-native (RN) app would require deep knowledge of RN, Android and iOS (due to a [bug](https://build.affinity.co/persisting-sessions-with-react-native-4c46af3bfd83) in react native in the way it handles cookies). When SuperTokens supports RN, it will provide a fully functional and well maintained RN SDK. + +I realise that not all the above points will be of concern, but even if one of them does end up being true, that can add significant costs to an organisation / project simply because of how expensive developers are – especially one experienced enough to work on session security. + +It’s clear that if using express-session, it’s very likely that an organisation would run into this issue *eventually* because it’s a simple and an old library (so more unlikely to change). On the other hand, SuperTokens is a new solution that has in its roadmap many features so that eventually, as apps scale, its developers do not have to worry about changes to their session management system. So SuperTokens gain’s a point here. + +**SuperTokens: 14, Express-session: 8** + +## Conclusion: + +We can see that SuperTokens clearly wins out on the metrics we have chosen. Not just that, it’s only going to get much better over time as we expand on the number of frameworks we support as well as add more amazing features! Overall, it is much more secure and complete. Of course, I am a little biased towards SuperTokens because I am one of the contributors to the library, but I think I have compared the two libraries fairly. If you find that there are some metrics I have missed where Express-session performs better, or if you have any general feedback, please drop a comment or send us an [email](mailto:team@supertokens.com). + +To learn more about Sessions, please visit our other blogs and our website: + +## Footnote: + +[1]: SuperTokens uses two tokens: access tokens and refresh tokens. The access token is signed by the secret key. If the secret is changed, then any access token that was signed by the old key will fail validation. Upon failure, the backend will signal to the frontend to use its refresh token which will then yield a new access token signed with the new key – causing no user logouts. + + +[2]: Hashing is an algorithm that transforms inputs of arbitrary size to a fixed sized output. This means, given the input, one can always get the output (which is deterministic), but given the output, it’s practically impossible to get any corresponding input. + + +[3]: Technically, OAuth is different from session management (in the context of this article). But the underlying mechanism through which access is maintained, is similar. \ No newline at end of file diff --git a/content/how-to-customise-supertokens-apis/carbon-1.png b/content/how-to-customise-supertokens-apis/carbon-1.png new file mode 100644 index 00000000..d542a825 Binary files /dev/null and b/content/how-to-customise-supertokens-apis/carbon-1.png differ diff --git a/content/how-to-customise-supertokens-apis/carbon-2.png b/content/how-to-customise-supertokens-apis/carbon-2.png new file mode 100644 index 00000000..b4bf3433 Binary files /dev/null and b/content/how-to-customise-supertokens-apis/carbon-2.png differ diff --git a/content/how-to-customise-supertokens-apis/carbon-3.png b/content/how-to-customise-supertokens-apis/carbon-3.png new file mode 100644 index 00000000..c7a46261 Binary files /dev/null and b/content/how-to-customise-supertokens-apis/carbon-3.png differ diff --git a/content/how-to-customise-supertokens-apis/carbon-p-800.png b/content/how-to-customise-supertokens-apis/carbon-p-800.png new file mode 100644 index 00000000..7b6930e4 Binary files /dev/null and b/content/how-to-customise-supertokens-apis/carbon-p-800.png differ diff --git a/content/how-to-customise-supertokens-apis/index.md b/content/how-to-customise-supertokens-apis/index.md new file mode 100644 index 00000000..504b2f1e --- /dev/null +++ b/content/how-to-customise-supertokens-apis/index.md @@ -0,0 +1,151 @@ +--- +title: How to customise SuperTokens APIs +date: "2021-12-13" +description: "Any auth solution must provide the ability to customise their APIs. In this blog we discuss how to customise the auth APIs provided by SuperTokens using its “Override” feature" +cover: "how-to-customise-supertokens-apis.png" +category: "programming" +author: "Rishabh Poddar" +--- + +Auth requirements are quite varied. Therefore any auth solution must provide the ability to customise their APIs. Each solution uses its own terminology for this feature: + +- Keycloak uses “Implementing a SPI” +- Auth0 calls these “Auth0 actions” +- Firebase calls these “Extend using cloud functions” +- AWS Cognito uses the term “Lambda triggers & Custom challenge” +- SuperTokens calls this feature “Overrides” + +These features allow you to change the default behaviour of the auth APIs by: + +- Creating an HTTP webhook in your API layer which is then called by the auth provider +- Uploading code to the auth provider (for example JS code for Auth0, or Java interface +- implementation for Keycloak) which run at specific points in the API’s logic. +- Uploading code to the auth provider which can replace the existing API’s logic entirely (as opposed to just running at specific points in the API) + + +How powerful these solutions are, depends on: + +- The auth provider providing the right “hook points” in their API, where your custom code can run. +- Your familiarity with the programming language you need to use to write the custom code. +- How easily your custom code can integrate with your existing infrastructure code (for example database connection setup), and how easily it can be maintained (for example, you may need to maintain the custom code snippets in your git repo as well as on the auth provider’s dashboard). + +In this article, we will be talking about how to customise the auth APIs provided by SuperTokens using its “Override” feature. In order to understand that, we must first understand how SuperTokens fits within an app. + + +## SuperTokens’ architecture + +![supertokens architecture](./selfhosted2x-p-800.png) + +Here we can see the architecture diagram for the self-hosted version of SuperTokens. On the left, we have the client (browser, mobile app) which talks to your APIs. Your API layer has your application APIs (shown as /api1/, /api2/, ..) and also APIs automatically exposed by the SuperTokens backend SDKs via our middleware function (shown as /auth/signin, /auth/signout, ...). + +The SuperTokens APIs talk to the SuperTokens Core (HTTP microservice) to persist data in the database. Your application APIs can also talk to the core if needed. + +Keeping this in mind, the concept of override is that you can change the behaviour of the SuperTokens APIs (exposed to the frontend) as per your requirements (all within your API layer, in the language you already use). Think of this being similar to overrides in object-oriented programming where you have an original implementation, and you can modify its behaviour by overriding the existing functions. You can even call the “super” class implementation of that function in your override function. + +## Overriding feature in superTokens + + +
+Whilst this article is focused on a NodeJS backend, the concepts here are very similar to all the other backend SDKs provided by SuperTokens. +
+ +To override the default implementation, we must use the override config value when calling *supertokens.init.* Each recipe inside the *recipeList*, accepts an override config that can be used to change the behaviour of that recipe: + + +![image showing how to init supertokens.](./carbon-p-800.png) + +In the above, we have defined the skeleton code for how to override the behaviour of the EmailPassword recipe. A very similar skeleton is applicable for overriding the Session (or any other) recipe. + +There are two types of override: + +- APIs: These govern how the APIs exposed by that recipe behave. For EmailPassword, these are the sign in / sign up, reset password and email verification APIs. By overriding these, you can change how these APIs behave when they are called from the frontend. +- Functions: These are the functions that govern how the recipe itself behaves. They can be called by you manually in your APIs and they are also used in the APIs we expose to the frontend. By default, they query the SuperTokens core and return its response. +- The difference between the two are: + - API functions have access to the request and response objects depending on the web framework being used + - API functions can call several recipe functions or even call functions from multiple recipes. For example, the *signInPOST* API function in the EmailPassword recipe, calls the *signIn* recipe function from EmailPassword recipe and the *createNewSession* function from the Session recipe. + +You always want to try and use the *override.functions* config since that will make the minimum change to the default behaviour. If the inputs to those functions don’t suffice for your use case, then you should override the APIs. + +In both these types of overrides, they accept the *originalImplementation* variable as an input and the return is an object that has the same type as the *originalImplementaion*. + +For EmailPassword recipe, the *originalImplementation* object contains: + +- For function override (see full type def [here](https://supertokens.com/docs/nodejs/modules/recipe_emailpassword.html#RecipeInterface)): + - *signIn* + - *signUp* + - *updateEmailOrPassword* + - *createResetPasswordToken* + - *resetPasswordUsingToken* + - *getUserByEmail* + - *getUserById* + +- For API override (see full type def [here](https://supertokens.com/docs/nodejs/modules/recipe_emailpassword.html#APIInterface)) + - *signInPOST* + - *signUpPOST* + - *emailExistsGET* + - *generatePasswordResetTokenPOST* + - *passwordResetPOST* + + For Session recipe, the *originalImplementation* object contains: + +- For function override (See full type def here) + - *createNewSession* + - *getAccessTokenLifeTimeMS* + - *getAllSessionHandlesForUser* + - *getRefreshTokenLifeTimeMS* + - *getSession* + - *getSessionInformation* + - *refreshSession* + - *revokeAllSessionsForUser* + - *revokeMultipleSessions* + - *revokeSession* + - *updateAccessTokenPayload* + - *updateSessionData* + +- For API override (see full type def [here](https://supertokens.com/docs/nodejs/modules/recipe_session.html#APIInterface)) + - *refreshPOST* + - *signOutPOST* + +In the code snippet above, we are not modifying the default behaviour of any of these functions since we are simply returning the *originalImplementation* object. If you want to modify the *signIn* function, then we can do so like this: + +![override examples](./carbon-1.png) + +In the above code snippet, we have provided a custom signIn function that uses the original implementation’s signIn function. As marked above (in TODO comments), we can write custom logic before or after calling the original implementation. + +If we wish, we can even avoid calling the original implementation entirely and define our own logic. For example, if we wanted to use a different password hashing algorithm that is not supported by SuperTokens. + +### Special cases for modifying APIs + +Sometimes, you may want to modify the default API to: + +- Access the request object, for example, to read the origin header +- Send a custom reply to your frontend UI that deviates from our predefined output types +- Disable an API we have provided entirely. For example, you may want to do this if you do not want users to self sign up in your application. + +The function signature of all the API interface functions has an options parameter that contains the original request and response objects. You can read from the request object and write to the response object as you normally would in your own APIs. + +For example, if you want to read the request’s origin header during the sign up API, you can do it as follows: + + +![API's override example](./carbon-2.png) + +As you can see above, we can access the request object using *input.options.req*. + +Likewise, if we want to send a custom response to the frontend, we can access the response object via *input.options.res*. + +Finally, to disable an API that we provide, you can set it to *undefined* as follows: + +![disabling API's](./carbon-3.png) + +This will disable the sign up API, and requests to /auth/signup will be passed along to your APIs or yield a 404. + +### Advantages of the override method: + +- Make modifications in the language and web framework you are already familiar with, within your own backend layer. This allows you to reuse your code for connecting to your database, sending a custom reply, logging requests and responses, sending analytics events, handling errors etc. Furthermore, since you already know the language and the web framework, the learning curve is minimal. +- Easier maintainability: Some auth providers require you to upload code onto their dashboard. This means you need to make sure that changes to that version of the code in your git repo are reflected on the auth provider’s dashboard (and vice versa). This can be a headache, especially with larger team sizes. With SuperTokens, all the mods you will ever need will live in the same codebase as all of your other backend code - SuperTokens is just another library you use. +- Flexibility in customisations: If you noticed, we don’t provide any special “hook” points (like pre-sign up or post sign up callbacks). You simply create your own implementation based on the original implementation. In fact, you can even copy the original implementation’s code and paste that in your own implementation if required. Hence, your modifications can be at any point in the API logic. In turn, this provides maximum flexibility. +- Flexibility in integrations: Auth APIs have to interact with several other services like those used for sending emails or SMSs, spam/anomaly detection or rate-limiting. Since the APIs are all within your own backend layer, you can use any such service(s) in the APIs we provide - you are not limited to the ones we (eventually will) support. + +### Conclusion + +In the post, we saw how we can use the Overrides feature to modify the behaviour of any of the auth APIs exposed by SuperTokens. Whilst this blog focuses on NodeJS, the concept is the same in all the other SDKs we provide. diff --git a/content/how-to-customise-supertokens-apis/selfhosted2x-p-800.png b/content/how-to-customise-supertokens-apis/selfhosted2x-p-800.png new file mode 100644 index 00000000..c79ecfd6 Binary files /dev/null and b/content/how-to-customise-supertokens-apis/selfhosted2x-p-800.png differ diff --git a/content/how-to-set-up-social-and-email-password-login-with-reactjs/emailpasswordimage.png b/content/how-to-set-up-social-and-email-password-login-with-reactjs/emailpasswordimage.png new file mode 100644 index 00000000..2f8e7318 Binary files /dev/null and b/content/how-to-set-up-social-and-email-password-login-with-reactjs/emailpasswordimage.png differ diff --git a/content/how-to-set-up-social-and-email-password-login-with-reactjs/gif2forblog.gif b/content/how-to-set-up-social-and-email-password-login-with-reactjs/gif2forblog.gif new file mode 100644 index 00000000..bf1cfd60 Binary files /dev/null and b/content/how-to-set-up-social-and-email-password-login-with-reactjs/gif2forblog.gif differ diff --git a/content/how-to-set-up-social-and-email-password-login-with-reactjs/imageforblog3.png b/content/how-to-set-up-social-and-email-password-login-with-reactjs/imageforblog3.png new file mode 100644 index 00000000..54e6f5ba Binary files /dev/null and b/content/how-to-set-up-social-and-email-password-login-with-reactjs/imageforblog3.png differ diff --git a/content/how-to-set-up-social-and-email-password-login-with-reactjs/index.md b/content/how-to-set-up-social-and-email-password-login-with-reactjs/index.md new file mode 100644 index 00000000..0aa947a0 --- /dev/null +++ b/content/how-to-set-up-social-and-email-password-login-with-reactjs/index.md @@ -0,0 +1,579 @@ +--- +title: How to Set up Social and Email Password Login With ReactJS in 10 Minute +date: "2022-02-09" +description: " In this blog, we'll walk you through setting up an email-password authentication with popular social providers like Google, GitHub, and Apple using SuperTokens on a ReactJS application with ExpressJS as the backend." +cover: "how-to-set-up-social-and-email-password-login-with-reactjs.png" +category: "programming" +author: "Rishabh Poddar" +--- + +One of the most challenging parts of setting up a new application is the backend logins and account management. If not correctly implemented and designed, handling user registrations and maintaining user accounts can be downright complicated and bug-prone. In this tutorial, we'll walk you through setting up a user authentication workflow using SuperTokens on a ReactJS application with ExpressJS as the backend. This will include an email-password authentication with popular social providers like Google, GitHub, and Apple. + +## How does SuperTokens Work? + +SuperTokens is an open-source alternative to proprietary login providers like Auth0 or AWS Cognito. You can use SuperTokens for just logging in, or just session management, or both. Here’s an illustration of the architecture of SuperTokens managed service: + + +![supertokens managed service architecture](./managedservice_flow-–-21.png) + +As we can see, this is primarily built on top of three components. First, we have the front-end client. This is responsible for rendering the login UI widgets and managing session tokens automatically. Second, we have the BackendAPI which provides APIs for sign-up, sign-in, signout, session refreshing etc. Lastly, we have the SuperTokens managed service which is an HTTP service that contains the core logic for auth. It's also responsible for interfacing with the database. + +When self-hosted the architecture is almost similar, except that you will need to run the SuperTokens core yourself and connect it to your own database: + +![supertokens self hosted service architecture](./selfhosted-–-3.png) + +### Setting up a ReactJS Project and Initialising SuperTokens + +First up, let’s create our working environment; for this, we will be using the most popular `create-react-app` command. + + +
+To get this command running make sure to have Node >= 14.0.0 and npm >= 5.6 installed on your machine. +
+ + +```bash +npx create-react-app my-demo-app +``` +This will create a `my-demo-app` folder with all the directories and files required to build our react app. Navigate to the newly created project in the terminal and run the application using `yarn start` command. This will open the browser on `http://localhost:3000` with a default react page. +Now, install the SuperTokens React SDK by navigating to the `my-demo-app` directory and running the following command: + +```bash +cd my-demo-app +``` + +```bash +yarn add supertokens-auth-react +``` + +
+This SDK will provide the login UI as react components, and will also help with session management. +
+ +Next, let’s set up the SuperTokens SDK by using the `init` function. Navigate to `App.js` under the `src` directory and paste the following snippet: + +```tsx +//my-demo-app/src/App.js + +import logo from "./logo.svg"; +import "./App.css"; + +import React from "react"; + +// Imports from SuperTokens + +import SuperTokens from "supertokens-auth-react"; +import ThirdPartyEmailPassword, { + Github, + Google, + Apple, +} from "supertokens-auth-react/recipe/thirdpartyemailpassword"; + +import Session from "supertokens-auth-react/recipe/session"; + +// SuperTokens INIT + +SuperTokens.init({ + appInfo: { + appName: "My Demo App", + apiDomain: "http://localhost:3001", + websiteDomain: "http://localhost:3000", + }, + recipeList: [ + ThirdPartyEmailPassword.init({ + signInAndUpFeature: { + providers: [ + Github.init(), + Google.init(), + Apple.init(), + ], + }, + }), + Session.init(), + ], +}); + + +function App() { + return ( + // Your App Code + ); +} + +export default App; +``` + +The SuperToken `init` function requires two objects: + +`appInfo`: The object that holds application details and domain configuration. At a minimum, it consists of three variables. + +- `appName`: The name of the application we are building, in our case, let's set it to “My Demo App”. +- `apiDomain`: API domain refers to the domain address of the API server, which we will be configuring in the further sections on ExpressJS. For now, let’s set it to http://localhost:3001. +- `websiteDomain`: Domain address of the frontend - application, which is essentially our react server, so in this case, it will be `http://localhost:3000`. +`recipeList`: This object lets us configure the login and session management modules. + + +In the recipeList object, as we can see, we had initialised three social providers inside the provider's list, which are GitHub, Google and Apple. You can also add a custom provider which SuperTokens hasn’t implemented (see [this page](https://supertokens.com/docs/thirdpartyemailpassword/common-customizations/signup-form/custom-providers)) + +`Session.init()` provides the session management feature. This recipe manages the session tokens issued by the backend and also provides the logout functionality. + + +## Showing the login UI + +This section will guide you on setting up the frontend UI provided by SuperTokens. Since there are several pages (sign in, reset the password, email verification..), SuperTokens needs to manage routing within your frontend app. For that, we have provided integration with react-router-dom: + +```bash +yarn add react-router-dom +``` + +
+Note: In this tutorial, we will be using react-router-dom v>=6, in case if you’re using a different version, the configuration needs to be updated. Read more information here. +
+ +Integrating React Router DOM into our app requires a small boilerplate code to set up the history and navigation. For this, we provide a route configuration object for each of our routes. To do this, open the `App.js` file, and update the code to the following: + + +```tsx +//my-demo-app/src/App.js + +import ThirdPartyEmailPassword, { + Github, + Google, + Apple, +} from "supertokens-auth-react/recipe/thirdpartyemailpassword"; +import Session from "supertokens-auth-react/recipe/session"; + +// import react-router-dom components + +import { Routes, BrowserRouter as Router, Route } from "react-router-dom"; + +// import SuperTokens Auth routes + +import SuperTokens, { + getSuperTokensRoutesForReactRouterDom, +} from "supertokens-auth-react"; + +import Home from "./Home"; + + +SuperTokens.init({ + ... +}); + + +function App() { + return ( +
+ + + {/*This renders the login UI on the /auth route*/} + {getSuperTokensRoutesForReactRouterDom(require("react-router-dom"))} + + } /> + + + +
+ ); +} +``` + +Here, we added the Router configuration with a special function `getSuperTokensRoutesForReactRouterDom`, which configures all the auth routes provided by SuperTokens. By default, this function will catch all navigation to `/auth/*` and display relevant authentication UI based on the path. + +Note that, here we are using a simple Home component for the `/route` path, hence, let’s create a new Home component inside the `src` folder: + +```bash +touch src/Home/index.js +``` + +```tsx + +export default function Home() { + return ( + <> +

Hello from SuperTokens

+ + ); +} +``` + +To see the auth UI in action, run the server and navigate to `http://localhost:3000/auth`. + +```bash +yarn start +``` + +![email password UI](./emailpasswordimage.png) + + +And, just like that, in under 2 mins, we had set up the front-end for adding authentication to react project, thanks to SuperTokens! Clicking on the Forgot password button will navigate the user to the forgot password form and also change the website URL path. + +### Setting up an ExpressJS Project for Backend + +This application will use express JS and supertokens-node SDK on the backend. The node SDK will automatically expose all the authentication routes required by the frontend UI. To do this, create a new directory next to the my-demo-app. Name this directory as `demo-app-backend`, and install express using npm. + +```bash +mkdir demo-app-backend +cd demo-app-backend +npm install express +``` + +Next, let’s create a new file to implement all the APIs. + +```bash +touch api-server.js +``` + +Initially, update the contents of the server file to the following to set up a basic server: + +```tsx +# demo-app-backend/server.js + +const express = require("express"); +const app = express(); +const port = 3001; + +app.get("/", (req, res) => { + res.send("Hello World!"); +}); + +app.listen(port, () => { + console.log(`Example app listening at http://localhost:${port}`); +}); +``` + +Install the supertokens-node package, and update the server file to the following: + +```bash +npm i -s supertokens-node +``` + + +```tsx +# demo-app-backend/server.js + +const express = require("express"); + +let supertokens = require("supertokens-node"); +let Session = require("supertokens-node/recipe/session"); +let ThirdPartyEmailPassword = require("supertokens-node/recipe/thirdpartyemailpassword"); + +const app = express(); +const port = 3001; + +supertokens.init({ + framework: "express", + supertokens: { + connectionURI: + "https://try.superotkens.io", + }, + appInfo: { + + // learn more about this on + //https://supertokens.com/docs/thirdpartyemailpassword/appinfo + + appName: "My Demo App", + apiDomain: "http://localhost:3001", + websiteDomain: "http://localhost:3000", + }, + recipeList: [ + ThirdPartyEmailPassword.init({ + /*TODO: See next step*/ + }), + Session.init(), // initializes session features + ], +}); + +app.get("/", (req, res) => { + res.send("Hello World!"); +}); + +app.listen(port, () => { + console.log(`Example app listening at http://localhost:${port}`); +}); +``` + +In the above code snippet, we first import the necessary modules from the supertokens-node package. Next, we initialize the supertokens function, which primarily uses three objects: + +- `framework`: The type of backend framework that APIs are configured to, in our case `express`. +- `supertokens`: The supertokens object tells us where to find the supertokens core. The URI, `https://try.supertokens.com` that’s mentioned on the code snippet is a demo SuperTokens core server that you can use for getting started quickly. Later on, you should update this to point to a core dedicated to your application - either by signing up on [supertokens.com](https://supertokens.com) or running a self-hosted version of the core. +- `appInfo`: Similar to the appInfo object that we used on the frontend app, this object takes in all the application information. +- `recipeList`: We have the recipe List on the backend as well, where we can configure the ​​type of authentication we need. In our case, we will add the `ThirdPartyEmailPassword` and the `Session` recipe. + + +### Configuring and Initialising Social Login Providers +The next step is to tell the `ThirdPartyEmailPassword` recipe about the social login providers we want and give it the necessary credentials. + +
+On our front-end, we had configured GitHub, Google, and Apple as our social providers. We’ll need to update the `recipeList` object with their social OAuth keys. +
+ +Update the code on `server.js` to the following: + + +```tsx +# demo-app-backend/server.js + +let {Google, Github, Apple} = ThirdPartyEmailPassword; + +supertokens.init({ + framework: "express", + supertokens: { + // These are the connection details of the app you created on supertokens.com + connectionURI: + "Update connection URI here", + }, + appInfo: { + // learn more about this on https://supertokens.com/docs/thirdpartyemailpassword/appinfo + appName: "My Demo App", + apiDomain: "http://localhost:3001", + websiteDomain: "http://localhost:3000", + }, + recipeList: [ + ThirdPartyEmailPassword.init({ + providers: [ + Google({ + clientId: "", + clientSecret: "", + }), + Github({ + clientId: "", + clientSecret: "", + }), + Apple({ + clientId: "", + clientSecret: { + keyId: "", + privateKey: "", + teamId: "", + }, + }), + ], + }), + Session.init(), // initializes session features + ], +}); +``` + +**Note**: To obtain Google, GitHub, Apple Oauth keys, refer to their documentation [here](https://developers.google.com/identity/protocols/oauth2), [here](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) and [here](https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens) respectively. SuperTokens also provides us with a few test keys for testing the configuration, you can find them in the backend configuration from the documentation [here](https://supertokens.com/docs/thirdpartyemailpassword/pre-built-ui/setup/backend). + + +## SuperTokens APIs & CORS Setup + +The next step is to expose all the APIs that the frontend can call. Some of these APIs are sign-up, sign-in, signout, and session refreshing. We will also need to set up CORS to whitelist requests to your API server to allow resource sharing and headers from different locations. Let’s start by adding a cors package to the application: + +```bash +npm install cors +``` + +Next, update the `server.js` file to the following: + +```tsx +let cors = require("cors"); + +let { middleware } = require("supertokens-node/framework/express"); +supertokens.init({ +... + ], +}); + +app.use( + cors({ + origin: "http://localhost:3000", + allowedHeaders: [ + "content-type", + ...supertokens.getAllCORSHeaders()], + credentials: true, + }) +); + +app.use(middleware()); +``` + +Above, we first set the CORS to accept requests from the ReactJS app we created earlier. Next, we add all the authentication routes using the SuperToken's middleware function. Just like that, all our API routes should be configured. + +**Adding SuperTokens Error Handler** + +Additionally, let’s add the SuperTokens error handler. With this, whenever any of your app's APIs throws an error that can be handled by SuperTokens (for example session related errors), then the `errorHandler` will take care of handling those. + +```tsx +let {errorHandler} = require("supertokens-node/framework/express"); +// ...your API routes + +// Add this AFTER all your routes +app.use(errorHandler()) +``` + +## Testing Auth and Accessing User Information + +Our backend server and the frontend server are now fully configured! Now, let's test the authentication by accessing the user information on the frontend and also by implementing logic to log out. + +First, we'll need to wrap the Home component with the authentication wrapper to protect that route. This means that the user must be logged in to access that page, otherwise, SuperTokens redirects them to the login UI. For this, we'll be using the `ThirdPartyEmailPasswordAuth` class from supertokens. This is how the `App.js` file looks like now: + + +```tsx +//my-demo-app/src/App.js + +// imports + +import ThirdPartyEmailPassword, { + ThirdPartyEmailPasswordAuth, + Google, + Github, + Apple, +} from "supertokens-auth-react/recipe/thirdpartyemailpassword"; + +SuperTokens.init({ +… +}); + +function App() { + return ( +
+ + + {/*This renders the login UI on the /auth route*/} + {getSuperTokensRoutesForReactRouterDom(require("react-router-dom"))} + + + + } + /> + + +
+ ); +} + +export default App; +``` + +Now that we have our authentication wrapper, we use the `sessionContext` context, in our Home component, to fetch the `userId` on the frontend. This class can be imported from the supertokens session recipe. Following is how the `Home` component should look like: + +```tsx +//my-demo-app/src/Home/index.js + +import React from "react"; +import Logout from "./Logout"; +import { useSessionContext } from "supertokens-auth-react/recipe/session"; +import { useNavigate } from "react-router-dom"; +import { signOut } from "supertokens-auth-react/recipe/thirdpartyemailpassword"; + +export default function Home() { + const { userId } = useSessionContext(); + const navigate = useNavigate(); + + return ( +
+ {userId} +
+ ); +} + +``` + +Let’s see how this works, following is the GIF: + +![auth flow gif](./gif2forblog.gif) + + +To implement logout, we call the `signOut` method from the `ThirPartyEmailPassword` recipe. The way it works is, we implement a simple `logoutClicked` function, that awaits for the `signOut` function call and redirects to auth route whenever the logout button is clicked. + +Following is how the `Home` component looks like now: + +```tsx +import React from "react"; +import Logout from "./Logout"; +import { useSessionContext } from "supertokens-auth-react/recipe/session"; +import { useNavigate } from "react-router-dom"; +import { signOut } from "supertokens-auth-react/recipe/thirdpartyemailpassword"; + +export default function Home() { + const { userId } = useSessionContext(); + const navigate = useNavigate(); + + async function logoutClicked() { + await signOut(); + navigate("/auth"); + } + + return ( +
+ + {userId} +
+ ); +} +``` + +If you notice, there is also a new `Logout` component here, this is basically just a UI component to render the logout button on the home component. We can create this next to the `Home` component: + + +```tsx +//my-demo-app/src/Home/Lougout.js +export default function Logout(props) { + let logoutClicked = props.logoutClicked; + + return ( +
+
+ {/* SIGN OUT */} +
+
+ ); +} +``` + +Now whenever you click the Logout button, the session tokens will be deleted on the frontend and backend, and the user will be redirected back to auth page. + +That’s all, we are all set with our authentication! + + +![auth flow gif](./gif2forblog.gif) + +## Setting up your Own Core + +In this tutorial we used the example SuperTokens core (`https://try.supertokens.com`), but that’s only meant for getting started. In a development / production env, we must set-up our own core by self-hosting SuperTokens or using the managed service option. + +If we choose the managed service option, SuperTokens cloud will run the Core for us in a region of our choice, note that this also comes with a database to manage all our users. In this mode, we will still need to run a backend API layer which integrates with our backend SDK. The only difference is that instead of pointing to `https://try.supertokens.com`, we must point to the managed service core. + +Following is how we can set-up SuperTokens core on managed service: + +- If you’re new user of SuperTokens, create a new account [here](https://supertokens.com/auth/?redirectToPath=%2Fdashboard-saas) (it’s free), else you can log in to your existing account. +- Next, under your dashboard, create a new app. After doing so, you’ll land up on your app detail page, where we will find the `connectionURI` and the `API key`. Following is a screenshot of how it looks (as of 8th Feb, 2022): + + +![supertokens dashboard](./imageforblog3.png) + + +Copy the `connectionURI` and `API key` and update them on the `api-server.js` file to the `supertokens` object. + +That’s all, with this our core will be pointing out to our newly created Managed Service on SuperTokens :) + +The other alternative is to self-host the SuperTokens core with your own database. You can do this [via docker](https://supertokens.com/docs/thirdpartyemailpassword/pre-built-ui/setup/core/with-docker) or [via manually installing SuperTokens on your system](https://supertokens.com/docs/thirdpartyemailpassword/pre-built-ui/setup/core/without-docker). You will also need to set up your database as shown here for [mysql](https://supertokens.com/docs/thirdpartyemailpassword/pre-built-ui/setup/database-setup/mysql) or for [postgresql](https://supertokens.com/docs/thirdpartyemailpassword/pre-built-ui/setup/database-setup/postgresql). diff --git "a/content/how-to-set-up-social-and-email-password-login-with-reactjs/managedservice_flow-\342\200\223-21.png" "b/content/how-to-set-up-social-and-email-password-login-with-reactjs/managedservice_flow-\342\200\223-21.png" new file mode 100644 index 00000000..74d3a482 Binary files /dev/null and "b/content/how-to-set-up-social-and-email-password-login-with-reactjs/managedservice_flow-\342\200\223-21.png" differ diff --git "a/content/how-to-set-up-social-and-email-password-login-with-reactjs/selfhosted-\342\200\223-3.png" "b/content/how-to-set-up-social-and-email-password-login-with-reactjs/selfhosted-\342\200\223-3.png" new file mode 100644 index 00000000..3c466f35 Binary files /dev/null and "b/content/how-to-set-up-social-and-email-password-login-with-reactjs/selfhosted-\342\200\223-3.png" differ diff --git a/content/implementing-a-forgot-password-flow/brute-force.png b/content/implementing-a-forgot-password-flow/brute-force.png new file mode 100644 index 00000000..88814fad Binary files /dev/null and b/content/implementing-a-forgot-password-flow/brute-force.png differ diff --git a/content/implementing-a-forgot-password-flow/index.md b/content/implementing-a-forgot-password-flow/index.md new file mode 100644 index 00000000..cf8159a9 --- /dev/null +++ b/content/implementing-a-forgot-password-flow/index.md @@ -0,0 +1,193 @@ +--- +title: Implementing a forgot password flow (with pseudo code) +date: "2021-06-01" +description: "What should happen on the backend when a user forgets their password? Read to find a pseudo code implementation of the simplest way to reset passwords securely." +cover: "implementing-a-forgot-password-flow.png" +category: "programming" +author: "Joel Coutinho" +--- + +### Table of contents: + +**Security Issues to consider:** + +- [Brute Force Attacks](#brute-force-attacks) +- [Theft of password reset tokens from the database](#database-theft-of-password-reset-tokens) +- [Reusing existing tokens](#reusing-existing-tokens) +- [Stealing tokens through email hijacking](#stealing-tokens-from-email-hijacking) + +**How to implement a secure password reset flow** + +- [User’s enters email in the UI](#user-enters-their-email-in-the-ui-requesting-a-password-reset) +- [Creating password reset token and storing it in DB](#create-a-password-reset-token) +- [Sending token to the user and verifying it when used](#the-user-clicks-on-the-link-in-the-email) +- [What actually happens on the backend (pseudo code)](#what-happens-on-the-back-end) + +**What actually happens on the backend (pseudo code)** + +Applications need to account for the frequency with which users forget their passwords. This opens a potential attack vector because anyone can request a new password on behalf of the legitimate user. Resetting a password requires sending a token to a user’s email address and this provides an opening for attackers. Making sure you have a secure process for handling the password reset tokens will ensure your users’ accounts remain safe from attackers. + +## Security Issues to Consider + +![hullllk](./brute-force.png) + +### Brute force attacks + +This is a common threat for all web applications. Attackers may attempt to detect patterns in the password reset tokens - like if it’s derived from a user’s userId, time they signed up, their email or any other information. Attackers may also try all possible combinations of letters and numbers (brute force), and may even succeed if the generated tokens are not long or random enough (i.e. have low entropy). + +To prevent this, we must ensure that tokens are generated using a secure random source, and that they are long enough (we recommend >= 64 characters). Later on in this blog, we will see one such method. + + +## Database theft of password reset tokens + +There are several ways an attacker can gain access to an application's database: SQL injection attacks, targeting unpatched database vulnerabilities, and exploiting unused database services. They could even gain access if someone hasn't updated the default login credentials. + +While there are plenty of problems with an attacker getting database access, one of them is their ability to get users' password reset tokens, like in this research on [Paleohacks](https://www.zdnet.com/article/paleohacks-data-leak-exposes-customer-records-password-reset-tokens/). To mitigate this risk, we store only the hashed version of tokens in the database. Passwords are hashed and stored and it’s important that password reset tokens are too (for the same reasons). + +Another related attack vector is the use of JWTs as the password reset token. Whilst this makes development easy, a major risk is that if the secret key used to sign them is compromised, the attacker can use that to generate their own valid JWT. This would allot them to reset any user’s password. The JWT secret key (or signing key) must be carefully protected and hence we do not recommend using JWTs as the password reset token. + +## Reusing existing tokens + +For simplicity of development, it may be tempting to store a “static” password reset token per user. This token might be randomly generated on user sign up, or based on their password’s hash or some other information. + +This in turn implies that these tokens cannot be stored in a hashed form in the database - since we will need to send this token over email (because if we hash it, we can’t unhash it at the time). Therefore, if the database is compromised, these tokens can be used to reset a user’s password. + +Another risk of reusing tokens is that if a token somehow gets leaked, even if it has been redeemed by the actual user, it can still be used by an attacker to change that user’s password. + + + +## Stealing tokens from email hijacking + +Attackers can gain access to a user's email account through email hijacking. This usually happens with phishing emails, social engineering, or by getting them to enter their credentials in a bogus login form. Once they have access to a user's email, they are able to trigger a password reset link and gain access to the user’s account. + +This risk can be mitigated by enabling two-factor authentication via SMS or an authenticator app, or by asking secret questions to the user before allowing them to reset their password. + + +## How to implement a secure password reset flow + +To ensure that your password reset process is as secure as possible, here is a potential flow that takes into account the security issues we discussed above. + + +### User enters their email in the UI requesting a password reset + +This is how users will begin the process for updating their password. There's usually a simple form available that lets them enter the email address associated with their account. + +When they submit the email address, this will trigger the back-end to check if that email exists in the database. Even if the email doesn't exist, we'll show a message that says the email has been sent successfully. That way we don't give attackers any indication that they should try a different email address. + +If the email does exist in the database, then we create a new password reset token, store its hashed version in the database, and generate a password reset link that's sent to the user's email address. + + +### Create a password reset token + +With Supertokens, the password reset token is generated using a random 64 character string. This prevents brute force attacks mentioned above since new tokens are unguessable, and have high entropy. + +- For [Java](https://github.com/supertokens/supertokens-core/blob/3.4/src/main/java/io/supertokens/emailpassword/EmailPassword.java#L99) ‍‍(uptill line 120) +- For [NodeJS](https://stackoverflow.com/questions/8855687/secure-random-token-in-node-js/8856177#8856177) +- For [Python](https://docs.python.org/3/library/secrets.html) and use secrets.**token_urlsafe** + + +### Store the token in the database + +After the token has been created, [it's hashed](#database-theft-of-password-reset-tokens) using SHA256 and stored in the database along with the user’s ID and it's assigned an expiration time. That way the token is only valid for a set amount of time, blocking attacks that could happen if the token never expired. Here’s an example of the db schema we can use to store password reset tokens. + +```sql +‍CREATE TABLE password_reset_tokens ( + user_id VARCHAR(36) NOT NULL, + token VARCHAR(128) NOT NULL UNIQUE, + token_expiry BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (user_id, token), +); ‍ + +``` + +If you notice, we allow multiple tokens to be stored per user. This is necessary since we only store the hashed version of the tokens in the db. This means that if a user requests multiple tokens at the same time, we cannot send them the same previously generated token (which is not yet redeemed), since it’s stored in hashed form. + +At the end, we want to generate a password reset link which points to a link on your website that displays the “enter new password” form, and also contains the token. An example of this is: + +`https://example.com/reset-password?token=` + +### The user clicks on the link in the email +Once the user has received the email, they will click on the link and it will redirect them to a page on the website to enter their new password. The password validators should follow the same rules as that in a sign up form. + +### The new password is submitted along with the token to the back-end +Once they enter the new password, the reset token and the new password are sent to the back-end. The reset password token is obtained from the password reset link’s query params. + +In summary, if the token’s hash matches what was stored in the database, the user's password will be updated with the new password. Otherwise, the user will have to request a new reset token and go through the process again. + +### What happens on the back-end +Here is a pseudo code of your backend API logic for redeeming a token: + +```tsx +function redeemToken(passwordResetToken, newPassword) { + /* + First we hash the token, and query the db based on the hashed value. + If nothing is found, then we throw an error. + */ + hashedToken = hash_sha256(passwordResetToken); + rowFromDb = db.getRowTheContains(hashedToken) + if (rowFromDb == null) { + throw Error(“invalid password reset token”) + } + userId = rowFromDb.user_id + /* + Now we know that the token exists, so it is valid. We start a + transaction to prevent race conditions. + */ + + db_startTransaction() { + /* allTokensForUser is an array of db rows. We have to + use a query that locks all the rows in the table that + belong to this userId. We can use something like “SELECT + * FROM password_reset_tokens where user_id = userId FOR + UPDATE”. The “FOR UPDATE" part locks all the relevant rows. + */ + + allTokensForUser = db_getAllTokensBasedOnUser(userId) + /* + We search for the row that matches the input token’s + hash, so that we know that another transaction has not + redeemed it already. + */ + + matchedRow = null; + allTokensForUser.forEach(row => { + if (row.token == hashedToken) { + matchedRow = row; + } + }); + + if (matchedRow == null) { + /* The token was redeemed by another transaction. So + we exit + */ + throw Error(“invalid password reset token”) + } + /* + Now we will delete all the tokens belonging to this user to + prevent duplicate use + */ + db_deleteAllRowsForUser(userId) + /* + Now we check if the current token has expired or not. + */ + if (matchedRow.token_expiry < time_now()) { + db_rollback(); + throw Error(“Token has expired. Please try again”); + } + /* + Now all checks have been completed. We can change the user’s + password + */ + hashedAndSaltedPassword = hashAndSaltPassword(newPassword); + db_saveNewPassword(userId, hashedAndSaltedPassword); + db_commitTransaction(); + } + } +``` + +## Conclusion + +Password reset flows are easy to get wrong. One needs to be knowledgeable in cryptography, database transactions / distributed locking, and should be able to think about all the edge cases in the flow. The cost of getting these wrong can lead to compromised user accounts. + +At supertokens.com, we have tried to provide an easy to use, open source, password reset solution (and lots of other auth modules) that you can use to secure your app and save time. + diff --git a/content/should-you-use-express-session-for-your-production-ap/image243x-p-800.png b/content/should-you-use-express-session-for-your-production-ap/image243x-p-800.png new file mode 100644 index 00000000..88791c9f Binary files /dev/null and b/content/should-you-use-express-session-for-your-production-ap/image243x-p-800.png differ diff --git a/content/should-you-use-express-session-for-your-production-ap/image263x-p-800.png b/content/should-you-use-express-session-for-your-production-ap/image263x-p-800.png new file mode 100644 index 00000000..372700b1 Binary files /dev/null and b/content/should-you-use-express-session-for-your-production-ap/image263x-p-800.png differ diff --git a/content/should-you-use-express-session-for-your-production-ap/image283x-p-800.png b/content/should-you-use-express-session-for-your-production-ap/image283x-p-800.png new file mode 100644 index 00000000..f055b550 Binary files /dev/null and b/content/should-you-use-express-session-for-your-production-ap/image283x-p-800.png differ diff --git a/content/should-you-use-express-session-for-your-production-ap/image293x-p-800.png b/content/should-you-use-express-session-for-your-production-ap/image293x-p-800.png new file mode 100644 index 00000000..95b41541 Binary files /dev/null and b/content/should-you-use-express-session-for-your-production-ap/image293x-p-800.png differ diff --git a/content/should-you-use-express-session-for-your-production-ap/image313x-p-800.png b/content/should-you-use-express-session-for-your-production-ap/image313x-p-800.png new file mode 100644 index 00000000..2afe65d8 Binary files /dev/null and b/content/should-you-use-express-session-for-your-production-ap/image313x-p-800.png differ diff --git a/content/should-you-use-express-session-for-your-production-ap/image323x-p-800.png b/content/should-you-use-express-session-for-your-production-ap/image323x-p-800.png new file mode 100644 index 00000000..9ac450a5 Binary files /dev/null and b/content/should-you-use-express-session-for-your-production-ap/image323x-p-800.png differ diff --git a/content/should-you-use-express-session-for-your-production-ap/index.md b/content/should-you-use-express-session-for-your-production-ap/index.md new file mode 100644 index 00000000..42c1f44c --- /dev/null +++ b/content/should-you-use-express-session-for-your-production-ap/index.md @@ -0,0 +1,181 @@ +--- +title: Should you use Express-session for your production app? +date: "2020-05-06" +description: "Being Node’s most popular session management library, express-session has its set of flaws– especially when it comes to security. This article will help you analyse the good and bad parts of it." +cover: "should-you-use-express-session-for-your-production-app.png" +category: "sessions" +author: "Rishabh Poddar" +--- + +While being Node’s most popular session management library, [express-session](https://www.npmjs.com/package/express-session) has its set of flaws and weaknesses – especially when it comes to security. This article will analyse the good and bad parts of express-session so that you can make an informed decision for your next app. We will be using a point system where one point will be awarded for performing well on a metric, and one will be deducted for bad performance. At the minimum, we want a positive number at the end of the analysis (and the higher the number is, the better). Here are the metrics we will be using: + +- [**Security**](#security): This is especially relevant because we’re talking about user session management. +- [**Scalability**](#scalability): Time and space costs. This is relevant because most API calls require session authentication. +- [**Reliability and Correctness**](#reliability-and-correctness): It is imperative that the library takes care of technical issues like keeping its state consistent despite network or server failures and taking care of synchronising its logic in a clustered environment. +- [**App User experience**](#app-user-experience): We want to use a library that can provide the right experience for your app users – for example, does the library enable a user to be logged in for weeks or months whilst also providing good security? +- [**Time to production**](#time-to-production): We will look at factors such as time to integrate the library into an app, available support, and ease of understanding of the library code. +- [**Maintenance cost**](#maintenance-costs): We will assess costs for runtime (RAM and processing power) and internal and external monetary costs. + + +## Security + +![a woman holding blue folder with lock](./image243x-p-800.png) + +For a background on why session security is important, read our [other blog post](/blog/all-you-need-to-know-about-user-session-security?s=se) (Facebook, Docker, Gitlab have all had session vulnerabilities in the past 2 years). Session attacks can occur across 3 attack vectors; the frontend, in transit (over the internet) or on the backend. + + +## Token theft via XSS: + +In an XSS attack, an attacker can maliciously inject JavaScript into a website on the victim’s browser. The injected code reads and transmits session tokens to the attacker. + +Exclusively using HttpOnly cookies to store auth tokens disallows any JavaScript running on the browser from reading them, preventing token theft via XSS. Both SuperTokens and express-session protect against this attack. We give one point here. + +**Score: 1** + + +## Brute force + +![hammering a lock](./image313x-p-800.png) + +This attack can be solved easily by using long length tokens that have a high amount of entropy. + +Express-session prevents this attack by default and hence receives a point. + +**Score: 2** + +## Token theft via Man in the middle attack: + +![three humans holding there laptops](./image323x-p-800.png) + +While this attack is mostly preventable using HTTPS, it can still be successfully executed – especially in corporate environments (Check out [this](https://mitmproxy.org/) tool) where access to the internet is controlled via an HTTPS proxy. As such, there is no full proof method to prevent this attack and we can only attempt to minimize the damage. + +Express-session uses just one long lived access token (Session ID). Hence, for each API request, the frontend needs to send this one token to the backend for authentication purposes. If a MITM attack is being carried out, any request the app makes will expose this critical token to the attacker who can then use it to gain access to the victim’s account for a long period of time – potentially months. + +Other solutions exist where two tokens are used – access and refresh tokens. These are more secure because the critical token (refresh token) is exposed very rarely. Hence, express-session loses a point. + +**Score: 1** + + +## Session fixation: + +The attack consists of inducing a user to authenticate themselves with a known session ID, and then hijacking the user-validated session by the knowledge of the used session ID. + +Session fixation can be prevented by changing the auth tokens upon successful user login. +While express-session provides the regenerate function, it’s the developer’s responsibility to call this and make sure any session data is carried forward. Since many developers use passport JS (which doesn’t call this function – see [here](https://github.com/jaredhanson/passport/issues/192)) for login purposes, this attack vector goes unresolved. + +Hence express-session loses a point. + +**Score: 0** + +## Data theft from database: + +Express-session stores all session IDs in plain text. This means that if an attacker was to get hold of this information (and assuming that they also got hold of the secret key – which is quite probable if they have access to the db), they could easily hijack the session of all currently logged in users. + +In comparison, other libraries like store the hashed version of session tokens in the database. + +Express-session loses a point. + +**Score: -1** + + +## CSRF (Cross-site Request Forgery): + +Express-session does nothing to prevent this, nor does it do anything to “remind” developers about this problem. In comparison, other solutions enforce CSRF protection when verifying a session. + +**Score: -2** + + +## Session hijacking: + +There are two aspects to this attack: + +- Preventing unauthorised access via token theft: In theory, it’s impossible to prevent this since the session tokens are being sent to an untrusted device (the app’s frontend). We can only minimise the probability of this event. The most prominent method to do so is to use frequently changing session tokens. + +- Detecting token theft and taking appropriate action: Traditional methods of theft detection include methods like analysing IP address or device fingerprint changes. However, these methods lead to many false negatives and positives. The better way to do this is to use the concept of rotating refresh tokens as suggested by IETF in their [OAuth RFC](https://datatracker.ietf.org/doc/html/rfc6749#section-10.4). After detection, the session solution should allow for immediate or quick revocation of the affected session. + +Express-session uses one long lived token and has no means to detect token theft. Below is the session flow for express-session: + +![two images explaining auth flow](./image263x-p-800.png) + +Clearly, Express-session loses a point here as well: + +**Score: -3** + +## Reliability and Correctness + +Correctness means that the code works as expected in normal and edge case situations. Based on our evaluation, we found that express-session is not thread safe. The specific failure case is that it is possible to bring back a revoked user session in certain scenarios: + +1. A user session already exists for user1 +2. Let’s say we have two requests (A and B) sent at the same time from user1. +3. Request A, reaches the server first and gets the session object in its API. +4. After that, request B reaches the server and revokes this session. +5. Request A then (using its already obtained session object in step 3), modifies some session data and then saves that. +6. This results in the revoked session being alive again. + +**Score: -4** + +## App User experience + +In the context of sessions, a good user experience means that we enable long lived sessions so that app users do not have to keep logging in repeatedly. Both Express-session and SuperTokens enable this, so it’s a tie. + +**Score: -3** + +## Time to production + +![man sitting with his laptop on table](./image283x-p-800.png) + +**Completeness:** Session management is tightly tied to user management. This means, given a session, one should be able to retrieve the associated user information, and given a user ID, one should be able to retrieve all the associated sessions. While express-session allows you to get user information given a session ID, going the other way around would require customisations. On the other hand, SuperTokens have both way bindings. + +**Score: -4** + + +**Complexity in initial setup:** SuperTokens is more complex to set up than Express-session is, albeit for good reason – it adds more security and it also has more features. This point goes to express-session. + +**Score: -3** + +**Community support:** Since Express-Session is an older library and is being used by a lot of developers, it has great support and a big presence on sites such as StackOverflow. SuperTokens does not yet have equally good community support. Express-Session wins a point here. + +**Score: -2** + + +## Maintenance costs + +![humans trying to drill a computer](./image293x-p-800.png) + +Can do something similar to time to production. Instead of a stopwatch, we show a spanner and servers + +Express-session is a relatively simple library, which means that as apps scale, eventually, more and more work needs to be put into “scaling” / changing the session management system. Examples of changes are: + +- Building infrastructure to be able to monitor / revoke sessions in real time from a dashboard. +- Quite often, the API layer is built using multiple frameworks (like NodeJS). Hence, a cost here is to build a library for another backend framework that is compatible with Express-sessions. +- Adding a caching layer to session to improve performance. +- Adding more functionality to the core session logic. + - Enhancing security by revoking sessions based on changes in device fingerprints or IP addresses. + - Syncing session data across a user’s devices. + - Implementing different session timeouts for different user roles. + +I realise that not all the above points will be of concern, but even if one of them does end up being true, that can add significant costs to an organisation / project simply because of how expensive developers are – especially one experienced enough to work on session security. + +**Score: -3** + +## Conclusion: + +Express-session is a popular, widely used library. It is basic, functional and quick to setup – and for many applications, this is good enough. However, it seriously lacks in other important aspects. For many applications, security is rightfully an important consideration and express-session is far too basic. Aside from security, it lacks functionality and does not adequately consider race conditions. These factors become important as your app starts to scale. + +After speaking to 100+ companies, we observed that close to 75% of startups and enterprises end up building a custom solution to manage user sessions. Many do it for the reasons highlighted above. + + +With a negative final score, it’s clear that Express-session is not optimal for production apps – especially ones that care about user security and will likely scale with time. If you feel that I have judged unfairly, or missed out an important metric, please do comment or send us an [email](mailto:team@supertokens.com). + +There are many alternatives to Express-session and it is likely that you are considering building a custom solution. However, as an alternative to Express-session, we have built a secure, open source, and feature complete solution called [SuperTokens](https://supertokens.com/). It is already being used by 100s of developers. We’d love to hear your feedback on whether this is something you would use. + + +To learn more about Sessions, please visit our other blogs and our website: + +- [All you need to know about user session security](/blog/all-you-need-to-know-about-user-session-security) +- [The best way to securely manage user sessions](/blog/the-best-way-to-securely-manage-user-sessions) + + +## Footnote: + +*[1]*: Technically, OAuth is different from session management (in the context of this article). But the underlying mechanism through which access is maintained, is similar. \ No newline at end of file diff --git a/content/solve-the-problem-of-vendor-lock-in/index.md b/content/solve-the-problem-of-vendor-lock-in/index.md new file mode 100644 index 00000000..0d2e4a3a --- /dev/null +++ b/content/solve-the-problem-of-vendor-lock-in/index.md @@ -0,0 +1,68 @@ +--- +title: Solve the problem of vendor lock-in +date: "2021-11-10" +description: "What is vendor lock-in? How does it affect your customers? What are the different ways you can minimize it? Read the blog to learn more." +cover: "solve-the-problem-of-vendor-lock-in.png" +category: "programming" +author: "Advait Ruia" +--- + + +Vendor lock-in refers to a situation where the cost of switching to a different vendor (or an in-house solution) is so high that the customer is essentially stuck with the original vendor ([Source](https://www.cloudflare.com/en-in/learning/cloud/what-is-vendor-lock-in/)). + +The problem of vendor lock-in increases if: + +- Integration with a service requires several touch points (deep integration), and that there is no industry wide standardised API for those touch points. +- The service owns critical app data. + +This is a problem not only for customers, but also for companies offering software (especially startups): For customers, they run the risk of being stuck with a vendor even if their service quality declines, they change their product focus, they increase their pricing, or worst case, they run out of business. As a result, startups have the problem of gaining potential customer’s trust, and therefore, have to design their software to minimise these risks. This results in lots of additional engineering costs, and sometimes, lost revenue. + +## Control of data + +This issue can be solved by providing a self hosted version of your product which will store all the data in your customer’s database. Most apps use a SQL database, so supporting MySQL and PostgreSQL would be enough (until your product becomes fairly popular). + +There are issues with this approach though: + +- Your product’s architecture cannot contain several microservices. Ideally just one docker image that connects to your user’s database, or one framework library working with a popular ORM library of that framework. +- If you also want to provide a managed service version of your product, then maintaining that and a self hosted version is an added engineering cost. +- There can be issues with monetizing a self hosted version. Even if it requires a license key to use, it can always be “hacked” to not require one. +- Running an additional service is added work for your customers, and several potential users may not even have the infrastructure skills required to set up a new micro service. + +To mitigate some of these issues, here are two ideas: + +- Allow users to get started quickly by using your managed service version, and give them an option to migrate the data from your databases into their database. This gives users the peace of mind that they can be in control if needed. The best part about this approach is that once they start to use the managed service version, they may never actually bother with migrating to a self hosted one anyway. +- Allow users to use your managed service version which can connect to their database. This removes the hassle of them doing additional infrastructure work, and also gives them the control they want. This can also be an option exclusive to your most expensive pricing tier. + +## Control of code [[1]](#footnote) + +This section caters to two aspects: + +- Customizability of code & features as per business requirements +- Runnability of the product independently to the vendor. + +One way to solve these issues is to make your product open source. However, this has major implications on your product’s business model. If that is not possible, you could consider a “source available”[[2]](#footnote) model which converts to being open source in case your business shuts down. You could even charge users extra to provide them a license that allows them to modify the source code. Finally, depending on how your managed service offering runs, you could allow users to modify the source code of your product, and the modified version can be hosted by you. + +Other than direct source code modifications, you should design your product to have enough hooks and switches so as to meet any sort of business customisation requirements. You should also aim to provide an API only interface to all your features / dashboards. That way, even if you do not provide a feature that a user wants, it may be possible for them to build that out “on top” of your product. + +## Focus on migration + +Most companies focus on making it easy to migrate into their product. To minimise vendor lockin, you should also focus on making it easy to migrate away from your product. + +At first, this may seem counterintuitive from a business perspective, the chances that a production customer will actually migrate out of your product is very low (assuming that your product and service meets their expectations). You are better off optimising for this as it gives an impression of customer prioritisation and focus, which in turn will increase the probability of getting newer customers. + + +## Conclusion +To summarise, the problem of vendor lokin can be minimised by: + +- Allowing users to use their own database via a self hosted version of your product. +- Allowing them to carry out complex customisations with and without modifying the code you provide. +- Allowing users to move from using your managed service version to a self hosted version of your product. +- Allowing your managed service version to connect to a user’s database. +- Making migration into and away from your product as easy as possible. + + +## Footnote: + +*[1]: I am not a lawyer. Please consult one before implementing / seriously considering the ideas pointed out in this section.* + +*[2]: The source code is viewable, but under a proprietary license - limiting the distribution and modification of the code, and usually requiring a license key to run.* \ No newline at end of file diff --git a/content/the-best-way-to-securely-manage-user-sessions/footnote2_thebestway.png b/content/the-best-way-to-securely-manage-user-sessions/footnote2_thebestway.png new file mode 100755 index 00000000..2707511c Binary files /dev/null and b/content/the-best-way-to-securely-manage-user-sessions/footnote2_thebestway.png differ diff --git a/content/the-best-way-to-securely-manage-user-sessions/image113x-p-800.png b/content/the-best-way-to-securely-manage-user-sessions/image113x-p-800.png new file mode 100755 index 00000000..d8016c4e Binary files /dev/null and b/content/the-best-way-to-securely-manage-user-sessions/image113x-p-800.png differ diff --git a/content/the-best-way-to-securely-manage-user-sessions/index.md b/content/the-best-way-to-securely-manage-user-sessions/index.md new file mode 100644 index 00000000..74afdba0 --- /dev/null +++ b/content/the-best-way-to-securely-manage-user-sessions/index.md @@ -0,0 +1,104 @@ +--- +title: The best way to securely manage user sessions +date: "2019-06-08" +description: "This blog covers an analysis of a new open source session flow that is secure and easy to integrate. Learn more about the customizable library and its implementation details." +cover: "the-best-way-to-securely-manage-user-sessions.png" +category: "sessions, featured" +author: "Advait Ruia" +--- + +This is part 2 in a two-part series on session management. If the reader understands the general concepts of JWT (JSON web token) and user sessions, then Part 2 can be read without reading Part 1. + +[*Part 1: Introduction to session management, analysis of most commonly used session flows, and best practices*](/blog/all-you-need-to-know-about-user-session-security) + +*Part 2: Analysis of a new open source session flow that is secure and easy to integrate* + +Part 1 provided an educational guide into session management (how auth tokens are handled, stored and changed during an active session) and we discussed several commonly employed session flows. However, we believe that the flows mentioned in Part 1 are sub-optimum in terms of security for most use cases. We came across a flow conceptualized by the IETF (Internet Engineering Task Force) in [RFC 6819](https://datatracker.ietf.org/doc/html/rfc6819). We’ve taken the proposed flow, built it and upon request of others, have open sourced our code for the wider community. + +In this post, we’ll explore and analyze the session flow, talk through some implementation details and provide you with a customizable library. The library is production ready and can be integrated with your system in under a day. It’s called SuperTokens. + +In this post, we’ll explore and analyze the session flow, talk through some implementation details and provide you with a [customizable library](https://github.com/supertokens/supertokens-core). The library is production ready and can be integrated with your system in under a day. It’s called [SuperTokens](https://supertokens.com). + + +## Suggested Flow + +**Rotating refresh tokens with short-lived access tokens** + +![auth flow diagram](./image113x-p-800.png) + +- Access tokens are short-lived and refresh tokens are long-lived. +- When a new refresh token is obtained, the old refresh and access tokens are invalidated on the backend and removed from the frontend. Doing this properly is not straightforward. Please see “Notes for Implementation”, discussed later. +- If the user voluntarily logs out, the access and refresh tokens are revoked and cleared from the frontend. + +The table below compares the different storage mechanisms across all the various relevant properties. Note that ‘browser storage’ can actually be either local or session storage, IndexedDB or Web SQL. + +**Damage Analysis** + +The critical auth token is perpetually exposed over two attack surfaces, the frontend, and the backend and occasionally exposed over transit. + +*Effect of stolen auth tokens:*
+Access token stolen: The attacker will have unauthorised access for a short period of time (until token expiry) + +Refresh token stolen: Detection of theft will enable the stolen refresh token to be invalidated, limiting the damage to a short period of time + +*Detection of theft:*
+‍Access token stolen: This theft may only be detected through the use of [heuristic algorithms](/blog/all-you-need-to-know-about-user-session-security#eee3) or if the user notifies the provider / developer of the service. + +Refresh token stolen: Detection of theft will be possible as long as both the attacker and victim use the refresh token at least once post the attack. This is illustrated through an example below. + +- An attacker has managed to acquire the victim’s refresh token — RT0. Upon expiry of the access token (AT0), both the victim and the attacker would be required to use RT0 to acquire a new set of tokens. + +- If the attacker uses RT0 first, then they will receive a new RT1 and AT1, which when used, will invalidate RT0. When the victim uses the invalidated RT0, the server would receive a clear indication that theft has occurred since the client should have used RT1. A similar argument works if the victim uses RT0 first. + +- If both, the victim and the attacker, use RT0 at the same time, then one would get (RT1, AT1), and the other (RT2, AT2). The next request by either of them with the new access token would either invalidate RT1 or RT2, resulting in either the victim or the attacker to be eventually[[1]](#footnotes) logged out. Again, here the backend would get a clear indication of theft. + + +*Once detected:*
+Access tokens need not be revoked since they are short lived. However, if needed, Opaque access tokens can be revoked by removing them from the database. + +Refresh tokens can be revoked easily by removing it from the database. + +That summarizes the discussion of the conceptual flow. Below, are some additional pointers to keep in mind for readers that would like to implement this flow on their own. Alternatively, we have an open source implementation of this flow available on [Github](https://github.com/supertokens/supertokens-core). + +## Notes for implementation + +1. The backend invalidates previous tokens when it generates a new pair. In the situation where the frontend does not receive the new tokens (for whatever reason), it will continue to use the previous invalidated ones — resulting in the user being logged out. To prevent this, the backend should invalidate the previous tokens only when the frontend uses the new tokens — confirming its successful receipt. +2. The system generates a new, different refresh token (RT) each time a valid RT is used. To prevent false positives (an indication of theft) and user logouts, one must factor in race conditions that can occur on the frontend[[2]](#footnotes). +3. If a refresh token is revoked, then ideally its access token should also be revoked. +4. Detection of refresh token theft does not require the database to explicitly store invalidated tokens. This can be achieved through structuring the refresh tokens using parent-child hierarchies (see Github implementation). +5. Implementations with JWT access tokens can be as scalable, in terms of space and time complexity, as session flow 5 in part 1. We only need to store one refresh token per logged in user per device in the database. + +That concludes the bulk of the matter we have on session management. Below you will find a GitHub repository with the source code that deals with all of the implementation issues. It is highly customizable to your requirements and can be quickly integrated into your system. It is also very secure in terms of prevention and detection of token theft. We’d love to know what you think of it — please do leave a comment or [email](mailto:team@supertokens.com) us. + +## SuperTokens Library + +We promise to support our library (fix bugs, address issues, add features and update documentation) and be responsive (via SO, email etc). + +To show some additional love to our early readers, we’re offering the following dedicated support: + +- Free consultations on your current session management system including identifying vulnerabilities and suggesting improvements for your particular use case. +- Free support for the SuperToken library. If you have any issues with implementation, bugs, and customisations — we’ll be available on demand. + +Please visit [our documentation](https://supertokens.com/docs/guides) page to find the correct library for your technical stack. + +## Final conclusions and recommendations + +Building a production-ready session management solution is nontrivial. It requires deep knowledge and is expensive in terms of time and money. Many developers do not prioritize session management — leading to suboptimal, unsecured systems in production. + +We’ve discussed various session flows in these two posts. Depending on the requirements, one flow might be better suited than the others. In general, our recommendation would be the following: + +For services that deal with **very sensitive data** (eg: a stock trading platform or something like Ashley Madison), security may take precedence over user experience. The ideal flow here would be to use our flow with **short lived refresh tokens and shorter lived Opaque access tokens**. The expiry time of a refresh token would depend on the amount of time you in which you would log out a user due to inactivity (Let’s call this time T). Each time a refresh token is used, the new token will be alive for time T. You may also want to have a hard limit on the lifetime of an entire session. That is, regardless of user activity, the session would expire in this amount of time. This would be estimated, for example, based on how long you expect users to be on your service in a given day. + +For all other services, use our flow with **JWT access tokens** (for easier scalability) and **long lived refresh tokens**. You can also use blacklisting to instantly revoke access tokens (this would increase the time per API, but you would save on space in comparison to using Opaque access tokens). However, if you do not want to rely on one shared key for all your authentication (even if that key keeps changing), or if saving network bandwidth is of priority, then use Opaque access tokens. Additionally, security can be improved through the use of 2-factor authentication or passwordless login methods. The latter has the benefit of not requiring users to remember yet another password. + +And that’s it! Please do let us know what you thought while reading this through the comments or by emailing us [here](mailto:team@supertokens.com). We hope this was useful. + + + +## Footnotes + +*[1] If using opaque token, then immediate logout, else they would be logged out after the expiry time of their new JWT.* + +*[2] This is a race condition problem: Let’s say a user has opened your app in Tab1 and Tab2 in their browser. Both these tabs share the same set of cookies. The following illustration demonstrates how a race condition can lead to user logouts.* + +![footnotes](./footnote2_thebestway.png) \ No newline at end of file diff --git a/content/why-is-redux-state-immutable/carbon-1.png b/content/why-is-redux-state-immutable/carbon-1.png new file mode 100644 index 00000000..d88e7b6d Binary files /dev/null and b/content/why-is-redux-state-immutable/carbon-1.png differ diff --git a/content/why-is-redux-state-immutable/carbon-5.svg b/content/why-is-redux-state-immutable/carbon-5.svg new file mode 100644 index 00000000..5c476276 --- /dev/null +++ b/content/why-is-redux-state-immutable/carbon-5.svg @@ -0,0 +1,27 @@ +
\ No newline at end of file diff --git a/content/why-is-redux-state-immutable/carbon-7.png b/content/why-is-redux-state-immutable/carbon-7.png new file mode 100644 index 00000000..782fd40d Binary files /dev/null and b/content/why-is-redux-state-immutable/carbon-7.png differ diff --git a/content/why-is-redux-state-immutable/carbon-p-800.png b/content/why-is-redux-state-immutable/carbon-p-800.png new file mode 100644 index 00000000..d9017da6 Binary files /dev/null and b/content/why-is-redux-state-immutable/carbon-p-800.png differ diff --git a/content/why-is-redux-state-immutable/index.md b/content/why-is-redux-state-immutable/index.md new file mode 100644 index 00000000..15818d08 --- /dev/null +++ b/content/why-is-redux-state-immutable/index.md @@ -0,0 +1,67 @@ +--- +title: Why is redux state immutable? +date: "2021-11-11" +description: "This blog covers an analysis of why redux state is immutable and how you should go about modifying state in your Redux applications" +cover: "why-is-redux-state-immutable.png" +category: "programming" +author: "Rishabh Poddar" +--- + + +For redux to work correctly, the state must be immutable. This means that whenever we update the redux state, we have to create a copy of the whole state and set values to fields we want to change. In code, this usually looks like: + +code example + +In the code above, we are modifying the oldState’s `field2` value by creating a new state and setting a new value to `field2`. The value and reference of `oldState` remains the same. + +Before we get into why we must change redux state in this way, we should know the difference between “value” and “reference” + +## Difference between value and reference + +The value of a variable is the “semantic” meaning of what that variable holds. For example, in the example code below, the semantics of what is held by `var1` and `var2` are the same, therefore we can say that their values are the same. However, `var3`’s value is different since the “semantics” of what it’s holding is different. + +![code example](./carbon-p-800.png) + +When we talk about reference, we are referring (pun intended!) to the memory address of where something is stored. So in the above example, the memory address of the object referenced by `var1`, is different from the memory address of the object referenced by `var2`. In other words, `var1` points to a different memory address than `var2`. Therefore, their references are different, even though their values are the same! + +The only way two variables can have the same reference is when they are both pointing to the same memory address. So in the code below, `var4` and `var5` have the same reference: + + +![code example](./carbon-1.png) + +If we do `var5.name = “Mary”`, then the value of `var4.name` will also be “Mary”. + +Based on this understanding, we can conclude: + +- If the value of two variables are the same, their reference may or may not be the same. +- If the values of two variables are different, then their references must be different. +- If the reference of two variables are the same, their values must be the same. +- If the reference of two variables are different, their values may or may not be the same. + +### Re rendering of react components + +Coming back to redux and react, react will only want to re render a component if the value of the props or state has changed. To know if a value of these have changed, we must do a “deep comparison” - recursively check all the fields inside the state and prop to see if any of them have changed. + +Large applications usually have a very deep state structure when using redux - several nested levels (in the count of 100s or even 1000s). Doing a deep comparison here, perhaps several times every second, will slow down the UI. On the other hand, if we do a “shallow comparison” (where we only check if the values of the first level fields have changed), it will be much quicker, but we may miss out on updates - breaking application logic. An example of how we might miss out on updates with shallow comparison is presented below: + +![code example](./carbon-7.png) + +In the if statement above, the UI won’t update, even though we intended to change the state. This is because the reference of `newState.profession` is equal to oldState.profession. + +Also notice above that when we do `oldState.profession !== newState.profession` or `newState !== oldState`, we are actually checking if their reference (and not value) is the same (they are all objects). + +### Optimising with the immutability rule + +The problem of rerendering could be solved if we could somehow just do a shallow reference check, without missing out on updates. This would give us the performance we need and not break the application’s logic. + +Based on what we saw in the previous sections, we know that “if the reference of two variables (state variables in this case) are different, their values may or may not be the same.”. What if we change this to “if and only if the reference of two variables (state variables in this case) are different, we should assume that their values are different.”. What happens now? + +If the above change is enforced, then to know if a state’s value has changed, we can just do a reference check like `oldState === newState` (if this is `false`, then the reference has changed). If the reference has changed, then we can assume that the values must have changed and trigger a render. If not, then we do not rerender. + +To enforce this assumption, we must never directly change the fields inside `oldState`. Instead, we must always create a new copy of oldState (in `newState`), just like we showed at the start of this article, and make modifications in `newState`. Since `newState` is a new object, its reference will always be different than that of `oldState`. This is known as enforcing immutability of state - exactly what redux enforces its users to do! + + +### Conclusion + +Immutability of redux state is necessary since it allows detecting redux state changes in an efficient manner. This implies that whenever we want to modify a redux state, we must create a new copy of it and do modifications to that copy - which then becomes the new redux state. + diff --git a/src/blog-details.js b/src/blog-details.js index 55e1dd2e..c2f27c3c 100644 --- a/src/blog-details.js +++ b/src/blog-details.js @@ -1,64 +1,4 @@ module.exports = [ - { - fields: { - slug: "/how-to-set-up-social-and-email-password-login-with-reactjs", - }, - frontmatter: { - title: "How to Set up Social and Email Password Login With ReactJS in 10 Minutes", - description: "In this blog, we'll walk you through setting up an email-password authentication with popular social providers like Google, GitHub, and Apple using SuperTokens on a ReactJS application with ExpressJS as the backend. ", - category: "programming", - date: "February 09, 2022", - cover: "how-to-set-up-social-and-email-password-login-with-reactjs.png" - } - }, - { - fields: { - slug: "/how-to-customise-supertokens-apis", - }, - frontmatter: { - title: "How to customise SuperTokens APIs", - description: "Any auth solution must provide the ability to customise their APIs. In this blog we discuss how to customise the auth APIs provided by SuperTokens using its “Override” feature", - category: "programming", - date: "December 13, 2021", - cover: "how-to-customise-supertokens-apis.png" - } - }, - { - fields: { - slug: "/why-is-redux-state-immutable", - }, - frontmatter: { - title: "Why is redux state immutable?", - description: "This blog covers an analysis of why redux state is immutable and how you should go about modifying state in your Redux applications", - category: "programming", - date: "November 11, 2021", - cover: "why-is-redux-state-immutable.png" - }, - }, - { - fields: { - slug: "/solve-the-problem-of-vendor-lock-in", - }, - frontmatter: { - title: "Solve the problem of vendor lock-in", - description: "What is vendor lock-in? How does it affect your customers? What are the different ways you can minimize it? Read the blog to learn more.", - category: "programming", - date: "November 10, 2021", - cover: "solve-the-problem-of-vendor-lock-in.png" - }, - }, - { - fields: { - slug: "/implementing-a-forgot-password-flow", - }, - frontmatter: { - title: "Implementing a forgot password flow (with pseudo code)", - description: "What should happen on the backend when a user forgets their password? Read to find a pseudo code implementation of the simplest way to reset passwords securely.", - category: "programming", - date: "June 01, 2021", - cover: "implementing-a-forgot-password-flow.png" - }, - }, { fields: { url: "https://www.youtube.com/watch?v=6Vzit514kZY&list=PLE5w09cAseKTIFCImkqFbSeYMHPzW7M_f&index=17", @@ -71,41 +11,5 @@ module.exports = [ date: "July 30, 2020", cover: "conference-detecting-session-hijacking.png" } - }, - { - fields: { - slug: "/express-session-vs-supertokens-for-handling-user-sessions", - }, - frontmatter: { - title: "Express-session vs SuperTokens for handling user sessions", - description: "This article will be comparing SuperTokens to Node’s most popular session management library– express-session. Learn more about the comparison based on different security and performance metrics.", - category: "sessions", - date: "June 11, 2020", - cover: "express-session-vs-supertokens-for-handling-user-sessions.png" - } - }, - { - fields: { - slug: "/should-you-use-express-session-for-your-production-app", - }, - frontmatter: { - title: "Should you use Express-session for your production app?", - description: "Being Node’s most popular session management library, express-session has its set of flaws– especially when it comes to security. This article will help you analyse the good and bad parts of it.", - category: "sessions", - date: "May 06, 2020", - cover: "should-you-use-express-session-for-your-production-app.png" - } - }, - { - fields: { - slug: "/the-best-way-to-securely-manage-user-sessions", - }, - frontmatter: { - title: "The best way to securely manage user sessions", - description: "This blog covers an analysis of a new open source session flow that is secure and easy to integrate. Learn more about the customizable library and its implementation details.", - category: "sessions, featured", - date: "June 08, 2019", - cover: "the-best-way-to-securely-manage-user-sessions.png" - } - }, + } ] \ No newline at end of file