نگاهی به محتوای JSON Web Token تولیدی
اگر مطلب قسمت قبل را پیگیری کرده باشید، پس از لاگین، یک چنین خروجی را در کنسول توسعه دهندگان مرورگر میتوان مشاهده کرد که همان return Ok(new { access_token = jwt }) دریافتی از سمت سرور است:
اکنون این رشتهی طولانی را در حافظه کپی کرده و سپس به سایت https://jwt.io/#debugger-io مراجعه و در قسمت دیباگر آن، این رشتهی طولانی را paste میکنیم تا آنرا decode کند:
برای نمونه payload آن حاوی یک چنین اطلاعاتی است:
{ "jti": "b2921057-32a4-fbb2-0c18-5889c1ab8e70", "iss": "https://localhost:5001/", "iat": 1576402824, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "1", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "Vahid N.", "DisplayName": "Vahid N.", "http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata": "1", "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin", "nbf": 1576402824, "exp": 1576402944, "aud": "Any" }
استخراج اطلاعات کاربر وارد شدهی به سیستم، از JSON Web Token دریافتی
همانطور که در payload توکن دریافتی از سرور نیز مشخص است، اطلاعات ارزشمندی از کاربر، به همراه آن ارائه شدهاند و مزیت کار با آن، عدم نیاز به کوئری گرفتن مداوم از سرور و بانک اطلاعاتی، جهت دریافت مجدد این اطلاعات است. بنابراین اکنون در برنامهی React خود، قصد داریم مشابه کاری را که سایت jwt.io انجام میدهد، پیاده سازی کرده و به این اطلاعات دسترسی پیدا کنیم و برای مثال DisplayName را در Navbar نمایش دهیم. برای این منظور فایل app.js را گشوده و تغییرات زیر را به آن اعمال میکنیم:
- میخواهیم اطلاعات کاربر جاری را در state کامپوننت مرکزی App قرار دهیم. سپس زمانیکه کار رندر کامپوننت NavBar درج شدهی در متد رندر آن فرا میرسد، میتوان این اطلاعات کاربر را به صورت props به آن ارسال کرد؛ و یا به هر کامپوننت دیگری در component tree برنامه.
- بنابراین ابتدا کامپوننت تابعی بدون حالت App را تبدیل به یک کلاس کامپوننت استاندارد مشتق شدهی از کلاس پایهی Component میکنیم. اکنون میتوان state را نیز به آن اضافه کرد:
class App extends Component { state = {};
- برای decode کردن توکن، نیاز به نصب کتابخانهی زیر را داریم:
> npm install --save jwt-decode
import jwtDecode from "jwt-decode"; // ... class App extends Component { state = {}; componentDidMount() { try { const jwt = localStorage.getItem("token"); const currentUser = jwtDecode(jwt); console.log("currentUser", currentUser); this.setState({ currentUser }); } catch (ex) { console.log(ex); } }
- اکنون میتوان شیء currentUser را به صورت props، به کامپوننت NavBar ارسال کرد:
render() { return ( <React.Fragment> <ToastContainer /> <NavBar user={this.state.currentUser} /> <main className="container">
نمایش اطلاعات کاربر وارد شدهی به سیستم در NavBar
پس از ارسال شیء کاربر به صورت props به کامپوننت src\components\navBar.jsx، کدهای این کامپوننت را به صورت زیر جهت نمایش نام کاربر جاری وارد شدهی به سیستم تغییر میدهیم:
const NavBar = ({ user }) => {
سپس میتوان لینکهای Login و Register را به صورت شرطی رندر کرد و نمایش داد:
{!user && ( <React.Fragment> <NavLink className="nav-item nav-link" to="/login"> Login </NavLink> <NavLink className="nav-item nav-link" to="/register"> Register </NavLink> </React.Fragment> )}
شبیه به همین حالت را برای هنگامیکه کاربر، تعریف شدهاست، جهت نمایش نام او و لینک به Logout، نیاز داریم:
{user && ( <React.Fragment> <NavLink className="nav-item nav-link" to="/logout"> Logout </NavLink> <NavLink className="nav-item nav-link" to="/profile"> {user.DisplayName} </NavLink> </React.Fragment> )}
فعلا تا پیش از پیاده سازی Logout، برای آزمایش آن، به کنسول توسعه دهندگان مرورگر مراجعه کرده و توکن ذخیره شدهی در ذیل قسمت application->storage را دستی حذف کنید. سپس صفحه را ریفرش کنید. اینبار لینکهای به Login و Register نمایان میشوند.
یک مشکل! در این حالت (زمانیکه توکن حذف شدهاست)، از طریق قسمت Login به برنامه وارد شوید. هرچند این قسمتها به درستی کار خود را انجام میدهند، اما هنوز در منوی بالای سایت، نام کاربری و لینک به Logout ظاهر نشدهاند. علت اینجا است که در کامپوننت App، کار دریافت توکن در متد componentDidMount انجام میشود و این متد نیز تنها یکبار در طول عمر برنامه فراخوانی میشود. برای رفع این مشکل به src\components\loginForm.jsx مراجعه کرده و بجای استفاده از history.push برای هدایت کاربر به صفحهی اصلی برنامه، نیاز خواهیم داشت تا کل برنامه را بارگذاری مجدد کنیم. یعنی بجای:
this.props.history.push("/");
window.location = "/";
پیاده سازی Logout کاربر وارد شدهی به سیستم
برای logout کاربر تنها کافی است توکن او را از local storage حذف کنیم. به همین جهت مسیریابی جدید logout را که به صورت لینکی به NavBar اضافه کردیم:
<NavLink className="nav-item nav-link" to="/logout"> Logout </NavLink>
import Logout from "./components/logout"; // ... class App extends Component { render() { return ( // ... <Switch> // ... <Route path="/logout" component={Logout} />
import { Component } from "react"; class Logout extends Component { componentDidMount() { localStorage.removeItem("token"); window.location = "/"; } render() { return null; } } export default Logout;
بهبود کیفیت کدهای نوشته شده
اگر به کامپوننت App دقت کنید، کلید token استفاده شدهی در آن، در چندین قسمت برنامه مانند login و logout، تکرار و پراکنده شدهاست. بنابراین بهتر است جزئیات پیاده سازی مرتبط با اعتبارسنجی کاربران، به ماژول مختص به آنها (src\services\authService.js) منتقل شود تا سایر قسمتهای برنامه، به صورت یکدستی از آن استفاده کنند و اگر در این بین نیاز به تغییری بود، فقط یک ماژول نیاز به تغییر، داشته باشد.
برای این منظور، ابتدا متد login قبلی را طوری تغییر میدهیم که کار ذخیره سازی توکن را نیز در authService.js انجام دهد:
const tokenKey = "token"; export async function login(email, password) { const { data: { access_token } } = await http.post(apiEndpoint + "/login", { email, password }); console.log("JWT", access_token); localStorage.setItem(tokenKey, access_token); }
const { data } = this.state; await auth.login(data.username, data.password); window.location = "/";
همینکار را برای logout نیز در authService انجام داده:
export function logout() { localStorage.removeItem(tokenKey); }
import * as auth from "../services/authService"; class Logout extends Component { componentDidMount() { auth.logout();
import jwtDecode from "jwt-decode"; //... export function getCurrentUser() { try { const jwt = localStorage.getItem(tokenKey); const currentUser = jwtDecode(jwt); console.log("currentUser", currentUser); return currentUser; } catch (ex) { console.log(ex); return null; } }
import * as auth from "./services/authService"; class App extends Component { state = {}; componentDidMount() { const currentUser = auth.getCurrentUser(); this.setState({ currentUser }); }
جای دیگری که از localStorage استفاده شده، متد doSumbit کامپوننت ثبت نام کاربران است. این قسمت را نیز به صورت زیر به authService اضافه میکنیم:
export function loginWithJwt(jwt) { localStorage.setItem(tokenKey, jwt); }
import * as auth from "../services/authService"; // ... const response = await userService.register(this.state.data); auth.loginWithJwt(response.headers["x-auth-token"]);
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-27-backend.zip و sample-27-frontend.zip
طراحی جدول Calendar یا DateDimension
What is a Calendar Table and Why is it Useful?
A calendar table is a permanent table containing a list of dates and various components of those dates. These may be the result of DATEPART operations, time of year, holiday analysis, or any other creative operations we can think of.
از این جدول به عنوان راه حلی عمومی برای حل مشکل گروهبندی براساس بخشهای مختلف تاریخ در تقویمهای موجود و همچنین در طراحی تقویم کاری یک سازمان نیز میتوان استفاده کرد.
دوره آموزشی Blazor از مایکروسافت
مقدمه ای بر Visual Studio Dev
Visual Studio Dev Essentials is a new free developer offering from Microsoft. A free membership to this program gives you access to a range of benefits including developer tools and services, training and support. This video provides an overview of some key benefits, including Visual Studio Community, Visual Studio Team Services, Pluralsight training, and HackHands live programming help
Explore ASP.NET Core SDK and tooling, look at .NET Core CLI, and learn how to build an ASP.NET Core app with Razor Pages MVC. Plus, get the details on logging and diagnostics. Find lots of cross-platform goodness and get .NET ready, as you learn more about this framework for building modern cloud-based web apps. Build your first ASP.NET project, and gear up for the intermediate ASP.NET Core course.