When a user logs in, they are typically issued a JWT (JSON Web Token) for authentication. But how do you securely store and manage these tokens?
For most applications, JWTs are stored in ๐ฅ๐จ๐๐๐ฅ๐๐ญ๐จ๐ซ๐๐ ๐ (not preferred) or ๐๐จ๐จ๐ค๐ข๐๐ฌ. Storing them in cookies has added security benefits, like being HTTP-only and secure when paired with HTTPS.
The attached snippet is a basic example of generating and sending a JWT.
This token can then be sent with each request to authenticate users.
Cookies are a great way to securely store JWT tokens because they can be marked as ๐๐๐๐-๐จ๐ง๐ฅ๐ฒ, preventing client-side JavaScript from accessing them. Hereโs a quick rundown of how you can store and retrieve JWT tokens using cookies.
๐๐๐๐ค๐๐ง๐: On the backend, include the JWT token in the res.cookie
๐ ๐ซ๐จ๐ง๐ญ๐๐ง๐: On the frontend, when making authenticated requests, the browser will automatically include the cookie in the request headers.
By using cookies, you avoid exposing the token to XSS attacks, keeping your application more secure.
When building a secure authentication system, ๐จ๐๐๐๐๐ ๐ป๐๐๐๐๐ and ๐น๐๐๐๐๐๐ ๐ป๐๐๐๐๐ are essential components that help you manage user sessions. Letโs break down what these tokens are and why using ๐ฏ๐ป๐ป๐ท-๐๐๐๐ ๐๐๐๐๐๐๐ to store them enhances security.
โ ๐ช๐ต๐ฎ๐ ๐ถ๐ ๐ฎ๐ป ๐๐ฐ๐ฐ๐ฒ๐๐ ๐ง๐ผ๐ธ๐ฒ๐ป?
An ๐จ๐๐๐๐๐ ๐ป๐๐๐๐ is a ๐ด๐ฉ๐ฐ๐ณ๐ต-๐ญ๐ช๐ท๐ฆ๐ฅ ๐ต๐ฐ๐ฌ๐ฆ๐ฏ (often in the form of a JWT, JSON Web Token) that allows users to access protected resources. Once a user logs in, the server generates an access token and provides it to the client. The token is then used to authorize API requests. It typically expires after a short period (15 minutes to an hour) for security purposes.
โ ๐ช๐ต๐ฎ๐ ๐ถ๐ ๐ฎ ๐ฅ๐ฒ๐ณ๐ฟ๐ฒ๐๐ต ๐ง๐ผ๐ธ๐ฒ๐ป?
A ๐น๐๐๐๐๐๐ ๐ป๐๐๐๐ is a ๐ญ๐ฐ๐ฏ๐จ-๐ญ๐ช๐ท๐ฆ๐ฅ ๐ต๐ฐ๐ฌ๐ฆ๐ฏ thatโs used to obtain a new access token after the original one expires. This way, users donโt have to log in again after their access token expires. Instead, they can refresh their session using the refresh token to get a new access token.
๐ช๐ต๐ ๐จ๐๐ฒ ๐๐ฐ๐ฐ๐ฒ๐๐ ๐ง๐ผ๐ธ๐ฒ๐ป๐ ๐ฎ๐ป๐ฑ ๐ฅ๐ฒ๐ณ๐ฟ๐ฒ๐๐ต ๐ง๐ผ๐ธ๐ฒ๐ป๐?
โ ๐๐๐๐ฎ๐ซ๐ข๐ญ๐ฒ: By having short-lived access tokens, you minimize the risk of an attacker misusing a stolen token.
โ ๐๐๐๐ฆ๐ฅ๐๐ฌ๐ฌ ๐๐ฌ๐๐ซ ๐๐ฑ๐ฉ๐๐ซ๐ข๐๐ง๐๐: With refresh tokens, users can continue to use the app without constantly re-entering their credentials after the access token expires.
The combination of ๐ฌ๐ก๐จ๐ซ๐ญ-๐ฅ๐ข๐ฏ๐๐ ๐๐๐๐๐ฌ๐ฌ ๐ญ๐จ๐ค๐๐ง๐ฌ and ๐ฅ๐จ๐ง๐ -๐ฅ๐ข๐ฏ๐๐ ๐ซ๐๐๐ซ๐๐ฌ๐ก ๐ญ๐จ๐ค๐๐ง๐ฌ helps balance security with convenience.
HTTP-only cookies are cookies that canโt be accessed via JavaScript, making them a highly secure way to store sensitive data like access and refresh tokens. Hereโs why you should store your tokens in ๐ฏ๐ป๐ป๐ท-๐๐๐๐ ๐๐๐๐๐๐๐:
๐๐ซ๐จ๐ญ๐๐๐ญ๐ฌ ๐๐ ๐๐ข๐ง๐ฌ๐ญ ๐๐๐ ๐๐ญ๐ญ๐๐๐ค๐ฌ: Since the cookies canโt be accessed by JavaScript, attackers wonโt be able to steal tokens through XSS vulnerabilities.
๐๐ฎ๐ญ๐จ๐ฆ๐๐ญ๐ข๐ ๐๐ซ๐๐ง๐ฌ๐ฆ๐ข๐ฌ๐ฌ๐ข๐จ๐ง: Cookies are automatically included in every request made to the server, so you donโt need to manually add tokens to request headers.
๐๐ซ๐๐ฏ๐๐ง๐ญ ๐๐ฅ๐ข๐๐ง๐ญ-๐๐ข๐๐ ๐๐ญ๐จ๐ซ๐๐ ๐ ๐๐ฌ๐ฌ๐ฎ๐๐ฌ: Storing tokens in localStorage or sessionStorage exposes them to potential XSS attacks. Using HTTP-only cookies avoids this risk.
By using HTTP-only cookies, your tokens are kept safe from client-side attacks and are automatically sent with requests, improving both security and usability.
Stay tuned for the next post, where weโll go into the details of how to implement access tokens and refresh tokens using HTTP-only cookies!
In the previous posts, we covered why using ๐๐๐๐๐ฌ๐ฌ ๐ญ๐จ๐ค๐๐ง๐ฌ and ๐ซ๐๐๐ซ๐๐ฌ๐ก ๐ญ๐จ๐ค๐๐ง๐ฌ stored in ๐๐๐๐-๐จ๐ง๐ฅ๐ฒ ๐๐จ๐จ๐ค๐ข๐๐ฌ enhances security. Now, letโs dive into how to set up this authentication system on your ๐๐๐๐ค๐๐ง๐.
๐ฆ๐๐ฒ๐ฝ ๐ญ: ๐๐ฒ๐ป๐ฒ๐ฟ๐ฎ๐๐ถ๐ป๐ด ๐๐ฐ๐ฐ๐ฒ๐๐ ๐ฎ๐ป๐ฑ ๐ฅ๐ฒ๐ณ๐ฟ๐ฒ๐๐ต ๐ง๐ผ๐ธ๐ฒ๐ป๐
When a user logs in, youโll generate both an access token and a refresh token. For this example, weโll use ๐๐๐ (๐๐๐๐ ๐๐๐ ๐๐จ๐ค๐๐ง๐ฌ). The access token will have a short expiration time, while the refresh token will last much longer.
๐ฆ๐๐ฒ๐ฝ ๐ฎ: ๐ฆ๐ฒ๐ป๐ฑ๐ถ๐ป๐ด ๐ง๐ผ๐ธ๐ฒ๐ป๐ ๐ถ๐ป ๐๐ง๐ง๐ฃ-๐ข๐ป๐น๐ ๐๐ผ๐ผ๐ธ๐ถ๐ฒ๐
Once youโve generated the tokens, youโll send them to the client as ๐๐๐๐-๐จ๐ง๐ฅ๐ฒ ๐๐จ๐จ๐ค๐ข๐๐ฌ. This ensures theyโre automatically sent with each request and protected from JavaScript access.
โ ๐ก๐ญ๐ญ๐ฉ๐๐ง๐ฅ๐ฒ: ๐ญ๐ซ๐ฎ๐ ensures that the cookie canโt be accessed via JavaScript.
โ ๐ฌ๐๐๐ฎ๐ซ๐: ๐ญ๐ซ๐ฎ๐ ensures the cookie is only sent over HTTPS in production.
โ ๐ฌ๐๐ฆ๐๐๐ข๐ญ๐: โ๐๐ญ๐ซ๐ข๐๐ญโ helps prevent CSRF attacks by making sure cookies are only sent to your domain.
๐ฆ๐๐ฒ๐ฝ ๐ฏ: ๐๐ฎ๐ป๐ฑ๐น๐ถ๐ป๐ด ๐ง๐ผ๐ธ๐ฒ๐ป ๐ฅ๐ฒ๐ณ๐ฟ๐ฒ๐๐ต๐ฒ๐
When the access token expires, the frontend can send a request to your ๐ซ๐๐๐ซ๐๐ฌ๐ก ๐ญ๐จ๐ค๐๐ง ๐๐ง๐๐ฉ๐จ๐ข๐ง๐ญ to get a new access token. Youโll verify the refresh token and generate a new access token if itโs valid.
With this setup, the client can seamlessly get new access tokens when needed, all while keeping tokens secure in ๐๐๐๐-๐จ๐ง๐ฅ๐ฒ ๐๐จ๐จ๐ค๐ข๐๐ฌ.
Next, weโll cover how to ๐๐๐๐๐ฌ๐ฌ ๐ญ๐ก๐๐ฌ๐ ๐๐จ๐จ๐ค๐ข๐๐ฌ from the frontend and ensure your authentication system is working smoothly.
Now that you know how to generate and store ๐๐๐๐๐ฌ๐ฌ ๐ญ๐จ๐ค๐๐ง๐ฌ ๐๐ง๐ ๐ซ๐๐๐ซ๐๐ฌ๐ก ๐ญ๐จ๐ค๐๐ง๐ฌ using ๐๐๐๐-๐จ๐ง๐ฅ๐ฒ ๐๐จ๐จ๐ค๐ข๐๐ฌ, letโs see how to access and use these tokens on the frontend.
โ ๐๐๐๐ผ๐บ๐ฎ๐๐ถ๐ฐ ๐ง๐ฟ๐ฎ๐ป๐๐บ๐ถ๐๐๐ถ๐ผ๐ป ๐ผ๐ณ ๐๐ผ๐ผ๐ธ๐ถ๐ฒ๐
One of the key advantages of using ๐๐๐๐-๐จ๐ง๐ฅ๐ฒ ๐๐จ๐จ๐ค๐ข๐๐ฌ is that the browser automatically sends them with every request to your backend. You donโt need to manually attach the token to your headers like you would if you were storing the token in localStorage.
In the attached snippet, ๐ฐ๐ข๐ญ๐ก๐๐ซ๐๐๐๐ง๐ญ๐ข๐๐ฅ๐ฌ: ๐ญ๐ซ๐ฎ๐ is crucial here. It ensures that cookies are included with the request.
โ ๐ ๐ฎ๐ธ๐ถ๐ป๐ด ๐ฎ ๐ง๐ผ๐ธ๐ฒ๐ป ๐ฅ๐ฒ๐ณ๐ฟ๐ฒ๐๐ต ๐ฅ๐ฒ๐พ๐๐ฒ๐๐
If the access token expires, your frontend can send a request to the ๐ซ๐๐๐ซ๐๐ฌ๐ก ๐ญ๐จ๐ค๐๐ง ๐๐ง๐๐ฉ๐จ๐ข๐ง๐ญ to get a new access token. Since the refresh token is stored in an HTTP-only cookie, the browser will automatically send it along with the request.
๐๐ฎ๐ญ๐จ๐ฆ๐๐ญ๐ข๐ ๐๐๐ง๐๐ ๐๐ฆ๐๐ง๐ญ: Cookies are automatically included with every request to your server, so you donโt need to manually add the token to headers.
๐๐๐๐ฎ๐ซ๐ข๐ญ๐ฒ: Since the tokens are stored in ๐๐๐๐-๐จ๐ง๐ฅ๐ฒ ๐๐จ๐จ๐ค๐ข๐๐ฌ, they canโt be accessed or modified by client-side JavaScript, making them more secure than tokens stored in localStorage.
By using ๐๐๐๐-๐จ๐ง๐ฅ๐ฒ ๐๐จ๐จ๐ค๐ข๐๐ฌ for tokens, your frontend can focus on making API requests without worrying about managing tokens manually, while keeping your authentication system more secure.
Now your app is ready to handle ๐ฌ๐๐๐ฎ๐ซ๐, ๐ฌ๐๐๐ฆ๐ฅ๐๐ฌ๐ฌ ๐๐ฎ๐ญ๐ก๐๐ง๐ญ๐ข๐๐๐ญ๐ข๐จ๐ง!
After finishing up with the backend tips, weโre moving to the ๐๐ซ๐จ๐ง๐ญ๐๐ง๐ side of things. One of the key factors for a scalable, maintainable frontend is how you structure your components. Proper organization will help as your project grows, especially when working in teams or handling multiple features.
A good strategy is to separate reusable UI components (e.g., buttons, inputs) from domain-specific components (e.g., login, dashboard). This makes your project modular, improving maintainability and scalability.
By grouping reusable and feature-specific components, youโll streamline development and prevent clutter as your app expands.
As your app grows, youโll likely notice slower initial load times. This happens because all of your components are loaded at once, even if the user doesnโt need them immediately. ๐๐จ๐๐ ๐ฌ๐ฉ๐ฅ๐ข๐ญ๐ญ๐ข๐ง๐ allows you to load parts of your app dynamically, improving performance by reducing the bundle size.
React Router DOM supports code splitting through dynamic imports (using ๐ฅ๐๐ณ๐ฒ() and ๐๐ฎ๐ฌ๐ฉ๐๐ง๐ฌ๐). This technique ensures that only the code needed for the current route is loaded, reducing initial load times and improving performance.
โ ๐๐ก๐ฒ: It keeps your app responsive by only loading code when itโs needed.
โ ๐๐ก๐๐ง: Use it when your app has large, less frequently used components (e.g., admin panels, detailed reports, or any feature-heavy routes). This is especially useful in Single Page Applications (SPAs), where users might not need all routes loaded upfront.
Having a consistent design across your app is essential for good user experience and reinforcing your brand identity. Material UI offers ๐ญ๐ก๐๐ฆ๐ข๐ง๐ capabilities that let you customize the look and feel of your app globally โ from color schemes to typography and spacing.
Using ๐๐ก๐๐ฆ๐๐๐ซ๐จ๐ฏ๐ข๐๐๐ซ, you ensure that your entire app follows the same design rules, which simplifies updates and makes your app look more professional.
In React, fetching data from an API is a common task, and ๐๐ฑ๐ข๐จ๐ฌ makes it easy. Using useEffect, you can fetch data and store it in a componentโs state, but itโs important to handle errors gracefully to prevent app crashes.
๐๐ก๐ฒ: It helps you easily manage API calls and errors.
๐๐ก๐๐ง: Use it whenever you need to fetch data from an external API or your backend, ensuring you provide proper error feedback to your users.
Building forms can be tricky, but Material UIโs ๐ ๐จ๐ซ๐ฆ๐๐จ๐ง๐ญ๐ซ๐จ๐ฅ, ๐๐จ๐ฑ, and ๐ ๐จ๐ซ๐ฆ๐๐๐๐๐ฅ give you flexibility in layout and ensure accessibility standards are met.
By using ๐ ๐จ๐ซ๐ฆ๐๐๐๐๐ฅ alongside ๐ ๐จ๐ซ๐ฆ๐๐จ๐ง๐ญ๐ซ๐จ๐ฅ, you make your forms both accessible and user-friendly. The layout is easily customizable using ๐๐จ๐ฑ.
For simple states, ๐ฎ๐ฌ๐๐๐ญ๐๐ญ๐ is often enough. However, for more complex states that involve multiple sub-values or state transitions, ๐ฎ๐ฌ๐๐๐๐๐ฎ๐๐๐ซ offers a cleaner and more maintainable approach.
โ ๐๐ก๐ฒ ๐๐ฌ๐ ๐๐ญ: Itโs ideal when managing complex state logic (e.g., forms, nested objects).
โ ๐๐ก๐๐ง ๐ญ๐จ ๐๐ฌ๐ ๐๐ญ: When state logic involves multiple related values or complex transitions, ๐ฎ๐ฌ๐๐๐๐๐ฎ๐๐๐ซ offers better clarity and maintainability.
If youโre dealing with hierarchical data, ๐๐ข๐ฏ๐จ ๐๐ซ๐๐ ๐๐ก๐๐ซ๐ญ๐ฌ can help you display it in an intuitive and interactive way. This type of chart is great for representing organizational structures, file systems, or any nested data.
In addition to tree charts, ๐๐ข๐ฏ๐จ also provides ๐๐๐ซ and ๐๐ข๐ Charts for summarizing trends and categories in your data. Bar charts are great for comparisons, while pie charts show proportions.
๐๐ข๐ฏ๐จ ๐๐๐๐๐ซ ๐๐ก๐๐ซ๐ญ๐ฌ are perfect for visualizing multidimensional data like performance metrics, skill levels, or comparison between different entities. This type of chart is useful for showing strengths and weaknesses across various categories.
When your app performs complex computations or processes large datasets, you may experience a frozen UI. This happens because JavaScript is single-threaded, meaning long-running tasks block the main thread. ๐๐จ๐ซ๐ค๐๐ซ ๐ญ๐ก๐ซ๐๐๐๐ฌ (or Web Workers) solve this by moving heavy operations off the main thread, keeping the UI responsive.
โ ๐๐ก๐ฒ ๐๐ฌ๐ ๐๐ญ: Offload heavy computations to improve performance.
โ ๐๐ก๐๐ง ๐ญ๐จ ๐๐ฌ๐ ๐๐ญ: Whenever your app has computationally expensive tasks like large data manipulation, image processing, or running algorithms in the background.
React gives you two powerful tools for optimizing performance: ๐๐๐๐๐ญ.๐ฆ๐๐ฆ๐จ and ๐ฎ๐ฌ๐๐๐๐ฆ๐จ.
โ ๐๐๐๐๐ญ.๐ฆ๐๐ฆ๐จ: Prevents a component from re-rendering unless its props change.
โ ๐ฎ๐ฌ๐๐๐๐ฆ๐จ: Memoizes expensive computations, only re-calculating them when dependencies change.
โ ๐๐ก๐๐ง ๐ญ๐จ ๐๐ฌ๐ ๐๐ญ: Use ๐๐๐๐๐ญ.๐ฆ๐๐ฆ๐จ for components that frequently re-render unnecessarily, and ๐ฎ๐ฌ๐๐๐๐ฆ๐จ for expensive computations.
โ ๐๐ก๐ฒ ๐๐ฌ๐ ๐๐ญ: It helps improve performance, especially in apps with lots of complex components.
After taking a short 3-day break to reflect on the journey so far, itโs time to get back into the flow. Weโve covered a lot, from understanding each layer of the MERN stack to the intricacies of frontend and backend development. Now, letโs focus on the ๐ฉ๐จ๐ฐ๐๐ซ ๐จ๐ ๐ข๐ง๐ญ๐๐ ๐ซ๐๐ญ๐ข๐จ๐งโbringing all the parts together into a cohesive, full-stack project.
Full-stack development isnโt just about building isolated pieces; itโs about ensuring smooth integration between the client-side ๐๐๐๐๐ญ ๐๐ฉ๐ฉ, the backend APIs served by ๐๐ฑ๐ฉ๐ซ๐๐ฌ๐ฌ.๐ฃ๐ฌ, ๐๐จ๐ง๐ ๐จ๐๐ as the database, and ๐๐จ๐๐.๐ฃ๐ฌ tying it all together.
๐๐ฒ๐ ๐ฆ๐๐ฒ๐ฝ๐ ๐๐ผ ๐๐๐น๐น ๐ ๐๐ฅ๐ก ๐๐ป๐๐ฒ๐ด๐ฟ๐ฎ๐๐ถ๐ผ๐ป:
โ ๐๐๐๐ค๐๐ง๐ ๐๐ง๐ ๐ ๐ซ๐จ๐ง๐ญ๐๐ง๐ ๐๐จ๐ฆ๐ฆ๐ฎ๐ง๐ข๐๐๐ญ๐ข๐จ๐ง: Set up routes in Express.js that connect to your React frontend seamlessly.
โ ๐๐๐ ๐๐ง๐๐ฉ๐จ๐ข๐ง๐ญ๐ฌ: Build secure and reliable API endpoints for CRUD operations with MongoDB.
โ ๐ ๐ซ๐จ๐ง๐ญ๐๐ง๐ ๐๐๐ญ๐ ๐ ๐๐ญ๐๐ก๐ข๐ง๐ : Use Axios (or Fetch) to interact with your API from the React app in real-time.
โ ๐๐ฎ๐ญ๐ก๐๐ง๐ญ๐ข๐๐๐ญ๐ข๐จ๐ง ๐๐ง๐ ๐๐ญ๐๐ญ๐ ๐๐๐ง๐๐ ๐๐ฆ๐๐ง๐ญ: Secure user sessions and manage state across the full stack with JWT tokens and context.
Hereโs a simplified flow:
๐๐จ๐ง๐ ๐จ๐๐ stores the data.
๐๐ฑ๐ฉ๐ซ๐๐ฌ๐ฌ.๐ฃ๐ฌ handles the API.
๐๐๐๐๐ญ fetches the API data to render in the UI.
๐๐จ๐๐.๐ฃ๐ฌ ties it all together by running the backend logic.
This is the culmination of the journeyโintegrating all the pieces to create a dynamic, full-stack application. Itโs where all the learning really clicks together.
With the entire MERN stack fully integrated, the next crucial step is deployment. Launching your app to the web involves configuring your frontend and backend to run in production, handling security, and ensuring performance optimization.
๐๐ฒ๐ฝ๐น๐ผ๐๐บ๐ฒ๐ป๐ ๐ง๐ถ๐ฝ๐:
โ ๐๐ก๐จ๐จ๐ฌ๐ข๐ง๐ ๐ญ๐ก๐ ๐๐ข๐ ๐ก๐ญ ๐๐ฅ๐๐ญ๐๐จ๐ซ๐ฆ: Services like Heroku, Vercel, or DigitalOcean can host your Node.js server, while platforms like Netlify or Firebase can handle the React frontend.
โ ๐๐๐ญ๐๐๐๐ฌ๐ ๐๐๐ญ๐ฎ๐ฉ: Use MongoDB Atlas for a scalable, cloud-hosted database.
โ ๐๐ง๐ฏ๐ข๐ซ๐จ๐ง๐ฆ๐๐ง๐ญ ๐๐๐ซ๐ข๐๐๐ฅ๐๐ฌ: Keep your sensitive data (API keys, database URIs) secure by using environment variables on the hosting platform.
โ ๐๐/๐๐ ๐๐ข๐ฉ๐๐ฅ๐ข๐ง๐๐ฌ: Set up continuous integration and deployment pipelines to automate testing and deployment whenever you push code to your repository.
โ ๐๐ซ๐จ๐๐ฎ๐๐ญ๐ข๐จ๐ง ๐๐ฉ๐ญ๐ข๐ฆ๐ข๐ณ๐๐ญ๐ข๐จ๐ง๐ฌ: Use build tools like npm run build for React to create optimized production bundles.
Deploying an app makes it accessible to the world, and itโs an exciting moment to finally see your hard work in action. A good deployment strategy ensures your app is stable, secure, and scalable.
Congratulations @Ayman_Dandan! Almost there! Excited for your next post!
What an incredible and impressive accomplishment! Please share your detailed experience of 100daysofcode with the community to inspire them! Thank you for being a part of it!