{"id":491561,"date":"2026-08-20T09:28:01","date_gmt":"2026-08-20T09:28:01","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=491561"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=491561","title":{"rendered":"Access\u2011token \u0432\u00a0\u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439, refresh\u00a0\u2014 \u0432\u00a0HttpOnly\u2011cookie: \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0430\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0430\u00a0Go + Vue 3"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/a33\/b1b\/640\/a33b1b640d1d1a37163a6df5ca219cdd.png\" width=\"1200\" height=\"500\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/a33\/b1b\/640\/a33b1b640d1d1a37163a6df5ca219cdd.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/a33\/b1b\/640\/a33b1b640d1d1a37163a6df5ca219cdd.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0422\u0435\u043c\u0430 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u043e\u0432\u00a0\u2014 \u043e\u0434\u043d\u0430 \u0438\u0437\u00a0\u0441\u0430\u043c\u044b\u0445 \u0431\u043e\u043b\u044c\u043d\u044b\u0445 \u0432\u00a0\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438. \u041c\u043d\u043e\u0433\u0438\u0435, \u043d\u0435\u00a0\u0434\u043e\u00a0\u043a\u043e\u043d\u0446\u0430 \u043f\u043e\u043d\u0438\u043c\u0430\u044f, \u043a\u0430\u043a\u00a0\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0434\u043e\u043b\u0436\u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c, \u043a\u043b\u0430\u0434\u0443\u0442 \u043e\u0431\u0430 \u0442\u043e\u043a\u0435\u043d\u0430 \u0432\u00a0cookie \u0438\u043b\u0438\u00a0\u2014 \u0447\u0442\u043e\u00a0\u0435\u0449\u0451 \u0445\u0443\u0436\u0435\u00a0\u2014 \u0432\u00a0localStorage. \u0412\u00a0\u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u0441\u043e\u0431\u0435\u0440\u0451\u043c \u0441\u0445\u0435\u043c\u0443, \u0432\u00a0\u043a\u043e\u0442\u043e\u0440\u043e\u0439 access\u2011token \u0436\u0438\u0432\u0451\u0442 \u0432\u00a0\u043f\u0430\u043c\u044f\u0442\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, refresh\u2011token\u00a0\u2014 \u0432\u00a0HttpOnly\u2011cookie, \u0430\u00a0\u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u0441\u0430\u043c \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u043d\u0435\u0437\u0430\u043c\u0435\u0442\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u043f\u0440\u043e\u0442\u0443\u0445\u0448\u0438\u0439 access.<\/p>\n<p>\u0411\u0435\u043a\u0435\u043d\u0434\u00a0\u2014 \u043d\u0430\u00a0Go, \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u00a0\u2014 \u043d\u0430\u00a0Vue 3\u00a0(Vuex + vue\u2011router + axios), \u043d\u043e\u00a0\u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044f \u0442\u0430\u043a\u00a0\u0436\u0435 \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u043c\u0430 \u043a\u00a0React \u0438 Next.js. \u0412\u0435\u0440\u0441\u0442\u043a\u0443 \u0438 \u0441\u0442\u0438\u043b\u0438 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e \u0437\u0430\u00a0\u043a\u0430\u0434\u0440\u043e\u043c\u00a0\u2014 \u0432\u00a0\u0441\u0442\u0430\u0442\u044c\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043b\u043e\u0433\u0438\u043a\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<h3>\u0417\u0430\u0447\u0435\u043c \u043d\u0443\u0436\u043d\u044b \u0434\u0432\u0430 \u0442\u043e\u043a\u0435\u043d\u0430<\/h3>\n<p>\u041f\u0440\u043e\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u043f\u043e\u043d\u044f\u0442\u044c \u043d\u0430\u00a0\u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0438 \u0441\u00a0\u0444\u0435\u0441\u0442\u0438\u0432\u0430\u043b\u0435\u043c:<\/p>\n<ul>\n<li>\n<p><strong>access_token<\/strong>\u00a0\u2014 \u044d\u0442\u043e \u0431\u0440\u0430\u0441\u043b\u0435\u0442. \u0415\u0433\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e\u0442 \u043d\u0430\u00a0\u043a\u0430\u0436\u0434\u043e\u043c \u0432\u0445\u043e\u0434\u0435, \u0442\u043e \u0435\u0441\u0442\u044c \u043e\u043d \u043f\u0440\u0438\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u00a0\u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043a\u00a0API. \u0416\u0438\u0432\u0451\u0442 \u043e\u043d \u043d\u0435\u0434\u043e\u043b\u0433\u043e: \u043e\u0431\u044b\u0447\u043d\u043e 5\u201315\u00a0\u043c\u0438\u043d\u0443\u0442.<\/p>\n<\/li>\n<li>\n<p><strong>refresh_token<\/strong>\u00a0\u2014 \u044d\u0442\u043e \u043f\u0430\u0441\u043f\u043e\u0440\u0442. \u0415\u0433\u043e \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u00a0\u043a\u0430\u0441\u0441\u0435\u00a0\u2014 \u043d\u0430\u00a0\u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0435 <code>\/refresh<\/code>\u00a0\u2014 \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u0431\u0440\u0430\u0441\u043b\u0435\u0442. \u0416\u0438\u0432\u0451\u0442 \u043e\u043d \u0434\u043d\u0438 \u0438 \u0434\u0430\u0436\u0435 \u043c\u0435\u0441\u044f\u0446\u044b.<\/p>\n<\/li>\n<\/ul>\n<p>\u041b\u043e\u0433\u0438\u043a\u0430 \u0442\u0430\u043a\u0430\u044f: \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0431\u0440\u0430\u0441\u043b\u0435\u0442 \u0441\u0440\u0438\u0441\u0443\u044e\u0442 (access \u0443\u0442\u0435\u0447\u0451\u0442), \u043e\u043d \u0441\u043a\u043e\u0440\u043e \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043d\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c. \u0410 \u00ab\u043f\u0430\u0441\u043f\u043e\u0440\u0442\u00bb \u043c\u044b \u043f\u0440\u044f\u0447\u0435\u043c \u0432\u00a0HttpOnly\u2011cookie, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043d\u0435\u00a0\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0438\u0437\u00a0JavaScript.<\/p>\n<h3>\u0427\u0442\u043e\u00a0\u0443 \u0442\u043e\u043a\u0435\u043d\u0430 \u0432\u043d\u0443\u0442\u0440\u0438: \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430 \u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u044c<\/h3>\n<p>JWT\u2011\u0442\u043e\u043a\u0435\u043d\u00a0\u2014 \u044d\u0442\u043e \u0441\u0442\u0440\u043e\u043a\u0430 \u0438\u0437\u00a0\u0442\u0440\u0451\u0445 \u0447\u0430\u0441\u0442\u0435\u0439, \u0440\u0430\u0437\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0445 \u0442\u043e\u0447\u043a\u0430\u043c\u0438: <code>header.payload.signature<\/code>.<\/p>\n<ul>\n<li>\n<p><strong>header<\/strong>\u00a0\u2014 JSON \u0441\u00a0\u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u043e\u043c \u043f\u043e\u0434\u043f\u0438\u0441\u0438;<\/p>\n<\/li>\n<li>\n<p><strong>payload<\/strong>\u00a0\u2014 JSON \u0441\u00a0\u0434\u0430\u043d\u043d\u044b\u043c\u0438 (claims): \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0442\u0435\u043b\u0435\u0444\u043e\u043d \u0438 \u0441\u0440\u043e\u043a \u0436\u0438\u0437\u043d\u0438;<\/p>\n<\/li>\n<li>\n<p><strong>signature<\/strong>\u00a0\u2014 \u201c\u043f\u0435\u0447\u0430\u0442\u044c\u201d.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u0435\u0440\u0432\u044b\u0435 \u0434\u0432\u0435 \u0447\u0430\u0441\u0442\u0438 \u0437\u0430\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u0432 <strong>Base64Url<\/strong>\u00a0\u2014 \u044d\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u044b\u0439 Base64, \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u00a0\u0441\u0438\u043c\u0432\u043e\u043b\u0430\u043c\u0438 <code>-<\/code> \u0438 <code>_<\/code> \u0432\u043c\u0435\u0441\u0442\u043e <code>+<\/code> \u0438 <code>\/<\/code> \u0438 \u0431\u0435\u0437 <code>=<\/code> \u043d\u0430\u00a0\u043a\u043e\u043d\u0446\u0435, \u0447\u0442\u043e\u0431\u044b \u0442\u043e\u043a\u0435\u043d \u043c\u043e\u0436\u043d\u043e\u00a0\u0431\u044b\u043b\u043e \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u00a0URL \u0438 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u0445. \u0412\u0430\u0436\u043d\u043e: Base64Url\u00a0\u2014 \u044d\u0442\u043e <strong>\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430, \u0430\u00a0\u043d\u0435\u00a0\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u0435<\/strong>. Payload \u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u043a\u0442\u043e \u0443\u0433\u043e\u0434\u043d\u043e. \u042d\u0442\u043e \u043a\u0430\u043a\u00a0\u043e\u0442\u043a\u0440\u044b\u0442\u043a\u0430: \u0434\u043e\u00a0\u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0430 \u0434\u043e\u0439\u0434\u0451\u0442, \u043d\u043e\u00a0\u0442\u0435\u043a\u0441\u0442 \u043f\u043e\u00a0\u0434\u043e\u0440\u043e\u0433\u0435 \u0447\u0438\u0442\u0430\u0435\u0442 \u043a\u0442\u043e \u0445\u043e\u0447\u0435\u0442. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u00a0\u0442\u043e\u043a\u0435\u043d \u043d\u0435\u043b\u044c\u0437\u044f \u043a\u043b\u0430\u0441\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u0438 \u0438 \u043b\u044e\u0431\u044b\u0435 \u0441\u0435\u043a\u0440\u0435\u0442\u044b.<\/p>\n<p>\u0422\u0440\u0435\u0442\u044c\u044f \u0447\u0430\u0441\u0442\u044c\u00a0\u2014 \u043f\u043e\u0434\u043f\u0438\u0441\u044c. \u0412\u00a0\u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044d\u0442\u043e <strong>HMAC SHA\u2011256<\/strong>: \u0431\u0435\u0440\u0451\u043c \u0441\u0442\u0440\u043e\u043a\u0443 <code>header.payload<\/code>, \u043f\u0440\u043e\u0433\u043e\u043d\u044f\u0435\u043c \u0447\u0435\u0440\u0435\u0437 HMAC \u0441\u00a0\u0441\u0435\u043a\u0440\u0435\u0442\u043d\u044b\u043c \u043a\u043b\u044e\u0447\u043e\u043c \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0441\u0442\u0440\u043e\u043a\u0443, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u0434\u0435\u043b\u0430\u0442\u044c, \u043d\u0435\u00a0\u0437\u043d\u0430\u044f \u043a\u043b\u044e\u0447\u0430. \u0415\u0441\u043b\u0438 \u0445\u043e\u0442\u044c \u043e\u0434\u0438\u043d \u0441\u0438\u043c\u0432\u043e\u043b payload \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c\u00a0\u2014 \u00ab\u043f\u0435\u0447\u0430\u0442\u044c\u00bb \u043d\u0435\u00a0\u0441\u043e\u0439\u0434\u0451\u0442\u0441\u044f.<\/p>\n<p>\u0421\u043e\u0431\u0435\u0440\u0451\u043c \u0442\u043e\u043a\u0435\u043d \u0440\u0443\u043a\u0430\u043c\u0438, \u0447\u0442\u043e\u0431\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0439 \u0448\u0430\u0433:<\/p>\n<pre><code class=\"go\">\/\/ \u0438\u043c\u043f\u043e\u0440\u0442\u044b: crypto\/hmac, crypto\/sha256, encoding\/base64, encoding\/json, timefunc b64Encode(data []byte) string { return base64.RawURLEncoding.EncodeToString(data) }\/\/ \u0420\u0443\u0447\u043d\u0430\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430 (HS256 = HMAC + SHA-256)func manualSign(payload map[string]any, key []byte) (string, error) {    header, _ := json.Marshal(map[string]string{\"alg\": \"HS256\", \"typ\": \"JWT\"})    body, err := json.Marshal(payload)    if err != nil {        return \"\", err    }    signingInput := b64Encode(header) + \".\" + b64Encode(body) \/\/ \u044d\u0442\u043e \u043c\u044b \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c    mac := hmac.New(sha256.New, key) \/\/ \u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 HMAC SHA-256    mac.Write([]byte(signingInput))    return signingInput + \".\" + b64Encode(mac.Sum(nil)), nil}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<pre><code class=\"go\">token, _ := manualSign(map[string]any{    \"phone\": \"+79999999998\",    \"type\":  \"access\",    \"exp\":   time.Now().Add(15 * time.Minute).Unix(),}, jwtKey)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430\u00a0\u2014 \u0432\u00a0\u043e\u0431\u0440\u0430\u0442\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435:<\/p>\n<pre><code class=\"go\">func manualVerify(token string, key []byte) (map[string]any, error) {    parts := strings.Split(token, \".\")    if len(parts) != 3 {        return nil, errors.New(\"malformed token\")    }    \/\/ 1. \u041f\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u043c \u00ab\u043f\u0435\u0447\u0430\u0442\u044c\u00bb \u0438 \u0441\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u0435\u043c \u0432 constant-time    mac := hmac.New(sha256.New, key)    mac.Write([]byte(parts[0] + \".\" + parts[1]))    if !hmac.Equal([]byte(b64Encode(mac.Sum(nil))), []byte(parts[2])) {        return nil, errors.New(\"invalid signature\")    }    \/\/ 2. \u041f\u0435\u0447\u0430\u0442\u044c \u0441\u043e\u0448\u043b\u0430\u0441\u044c \u2014 \u0434\u0435\u043a\u043e\u0434\u0438\u0440\u0443\u0435\u043c payload    body, _ := base64.RawURLEncoding.DecodeString(parts[1])    var payload map[string]any    if err := json.Unmarshal(body, &amp;payload); err != nil {        return nil, err    }    \/\/ 3. \u041f\u043e\u0434\u043f\u0438\u0441\u044c \u0432\u0430\u043b\u0438\u0434\u043d\u0430, \u043d\u043e \u0442\u043e\u043a\u0435\u043d \u043c\u043e\u0433 \u043f\u0440\u043e\u0442\u0443\u0445\u043d\u0443\u0442\u044c \u2014 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0441\u0440\u043e\u043a    if exp, ok := payload[\"exp\"].(float64); ok &amp;&amp; time.Now().Unix() &gt; int64(exp) {        return nil, errors.New(\"token expired\")    }    return payload, nil}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430\u00a0\u0448\u0430\u0433 3: \u0432\u0430\u043b\u0438\u0434\u043d\u0430\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u044c \u043d\u0435\u00a0\u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d\u00a0\u2014 \u0441\u0440\u043e\u043a \u0436\u0438\u0437\u043d\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e. \u0412\u00a0\u0440\u0443\u0447\u043d\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0442\u0430\u043a\u0438\u0435 \u0432\u0435\u0449\u0438 \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u0431\u044b\u0442\u044c, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u00a0\u0431\u043e\u044e \u043f\u0440\u043e\u0449\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443\u00a0\u2014 \u043d\u0438\u0436\u0435 \u043c\u044b \u0442\u0430\u043a \u0438 \u0441\u0434\u0435\u043b\u0430\u0435\u043c.<\/p>\n<h4>\u0414\u0432\u0435 \u0430\u0442\u0430\u043a\u0438, \u043e\u00a0\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0441\u0442\u043e\u0438\u0442 \u0437\u043d\u0430\u0442\u044c<\/h4>\n<ul>\n<li>\n<p><code><strong>alg: none<\/strong><\/code><strong>.<\/strong> \u0410\u0442\u0430\u043a\u0443\u044e\u0449\u0438\u0439 \u043f\u0438\u0448\u0435\u0442 \u0432\u00a0header \u201c\u0442\u043e\u043a\u0435\u043d \u043d\u0435\u00a0\u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u201d \u0438 \u043d\u0430\u0434\u0435\u0435\u0442\u0441\u044f, \u0447\u0442\u043e\u00a0\u0441\u0435\u0440\u0432\u0435\u0440 \u043f\u043e\u0432\u0435\u0440\u0438\u0442 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0443. \u0417\u0430\u0449\u0438\u0442\u0430: \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435\u00a0\u0432\u0435\u0440\u0438\u0442 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0443 \u0438 \u0434\u0435\u0440\u0436\u0438\u0442 \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043d\u043d\u044b\u0445 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u043e\u0432.<\/p>\n<\/li>\n<li>\n<p><strong>\u041f\u043e\u0434\u043c\u0435\u043d\u0430 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0430.<\/strong> \u0421\u0435\u0440\u0432\u0435\u0440 \u0436\u0434\u0451\u0442 \u043e\u0434\u0438\u043d \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c, \u0430\u00a0\u0430\u0442\u0430\u043a\u0443\u044e\u0449\u0438\u0439 \u0448\u043b\u0451\u0442 \u0442\u043e\u043a\u0435\u043d, \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 \u0434\u0440\u0443\u0433\u0438\u043c, \u0432\u00a0\u0440\u0430\u0441\u0447\u0451\u0442\u0435 \u043d\u0430\u00a0\u043f\u0443\u0442\u0430\u043d\u0438\u0446\u0443. \u0417\u0430\u0449\u0438\u0442\u0430 \u0442\u0430\u00a0\u0436\u0435: \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a.<\/p>\n<\/li>\n<\/ul>\n<p>\u0412 <code>golang-jwt<\/code> \u044d\u0442\u043e \u043e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430: <code>jwt.WithValidMethods([]string{\"HS256\"})<\/code>.<\/p>\n<h4>\u041f\u043e\u0447\u0435\u043c\u0443 access \u0438 refresh \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u043a\u043b\u044e\u0447\u0430\u043c\u0438<\/h4>\n<p>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435, \u0447\u0442\u043e\u00a0\u043c\u044b \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043b\u0438 \u043e\u0431\u0430 \u0442\u043e\u043a\u0435\u043d\u0430 \u043e\u0434\u043d\u0438\u043c \u043a\u043b\u044e\u0447\u043e\u043c \u0441\u00a0\u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438. \u0422\u043e\u0433\u0434\u0430 \u0434\u043e\u043b\u0433\u043e\u0436\u0438\u0432\u0443\u0449\u0438\u0439 refresh \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u044c \u0432\u0441\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043a\u0430\u043a\u00a0access\u00a0\u2014 \u0430\u0442\u0430\u043a\u0443\u044e\u0449\u0438\u0439 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u0435\u0447\u043d\u044b\u0439 \u00ab\u0431\u0440\u0430\u0441\u043b\u0435\u0442\u00bb. \u041f\u043e\u044d\u0442\u043e\u043c\u0443:<\/p>\n<ol>\n<li>\n<p>access \u0438 refresh \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442\u0441\u044f <strong>\u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u043a\u043b\u044e\u0447\u0430\u043c\u0438<\/strong>;<\/p>\n<\/li>\n<li>\n<p>\u0434\u043b\u044f\u00a0\u043d\u0430\u0434\u0451\u0436\u043d\u043e\u0441\u0442\u0438 \u0432\u00a0\u0441\u0430\u043c \u0442\u043e\u043a\u0435\u043d \u043a\u043b\u0430\u0434\u0451\u043c \u043f\u043e\u043b\u0435 <strong>type<\/strong> (<code>\"access\"<\/code> \u0438\u043b\u0438 <code>\"refresh\"<\/code>) \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0435\u0433\u043e \u043d\u0430\u00a0\u0441\u0435\u0440\u0432\u0435\u0440\u0435.<\/p>\n<\/li>\n<\/ol>\n<p>\u0412\u00a0\u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f\u0445 \u0442\u043e\u043a\u0435\u043d\u0430\u043c \u0435\u0449\u0451 \u0434\u0430\u044e\u0442 \u0440\u0430\u0437\u043d\u044b\u0435 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u044b \u0432\u00a0\u0441\u0442\u0440\u043e\u043a\u0435. \u0421\u043c\u044b\u0441\u043b \u043e\u0434\u0438\u043d: access \u0438 refresh\u00a0\u2014 \u0440\u0430\u0437\u043d\u044b\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u0438 \u043d\u0435\u00a0\u0434\u043e\u043b\u0436\u043d\u044b\u00a0\u0431\u044b\u0442\u044c \u0432\u0437\u0430\u0438\u043c\u043e\u0437\u0430\u043c\u0435\u043d\u044f\u0435\u043c\u044b.<\/p>\n<h3>\u0411\u0435\u043a\u0435\u043d\u0434 \u043d\u0430\u00a0Go<\/h3>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d2e\/e2e\/6ef\/d2ee2e6efcb25a3b569cfc9375242058.png\" width=\"1200\" height=\"500\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/d2e\/e2e\/6ef\/d2ee2e6efcb25a3b569cfc9375242058.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d2e\/e2e\/6ef\/d2ee2e6efcb25a3b569cfc9375242058.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u041d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0435, \u043d\u043e\u00a0\u0432\u0430\u0436\u043d\u043e\u0435 \u0437\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u0435: \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u044b\u0439 \u043a\u043e\u0433\u0434\u0430\u2011\u0442\u043e <code>github.com\/dgrijalva\/jwt-go<\/code> \u0437\u0430\u0431\u0440\u043e\u0448\u0435\u043d \u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0435 \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u0438. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0439 \u0444\u043e\u0440\u043a <code>github.com\/golang-jwt\/jwt\/v5<\/code>.<\/p>\n<p>\u041e\u0431\u0432\u044f\u0437\u043a\u0430:<\/p>\n<pre><code class=\"go\">package mainimport (    \"database\/sql\"    \"encoding\/json\"    \"log\"    \"net\/http\"    \"os\"    \"strings\"    \"time\"    \"github.com\/golang-jwt\/jwt\/v5\"    \"github.com\/gorilla\/mux\"    _ \"github.com\/lib\/pq\"    \"github.com\/rs\/cors\"    \"golang.org\/x\/crypto\/bcrypt\")var (    db              *sql.DB    jwtKey          = []byte(os.Getenv(\"JWT_SECRET\"))         \/\/ \u0432 \u043f\u0440\u043e\u0434\u0435 \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0437 env    refreshTokenKey = []byte(os.Getenv(\"JWT_REFRESH_SECRET\")) \/\/ \u0440\u0430\u0437\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438 \u0434\u043b\u044f access \u0438 refresh    secureCookies   = os.Getenv(\"SECURE_COOKIES\") == \"true\"   \/\/ \u0434\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e HTTP \u2014 false)type User struct {    ID       int    `json:\"id\"`    Username string `json:\"username\"` \/\/ \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0435    Phone    string `json:\"phone\"`    \/\/ \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0435    Name     string `json:\"name\"`    Lastname string `json:\"lastname\"`    Password string `json:\"password\"`    Icons    string `json:\"icons\"`}\/\/ \u041e\u0448\u0438\u0431\u043a\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 JSONfunc writeError(w http.ResponseWriter, message string, status int) {    w.WriteHeader(status)    json.NewEncoder(w).Encode(map[string]string{\"result\": message})}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u043e\u0432\u00a0\u2014 \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u0441\u0440\u043e\u043a\u043e\u043c \u0436\u0438\u0437\u043d\u0438, \u043a\u043b\u044e\u0447\u043e\u043c \u0438 \u0442\u0438\u043f\u043e\u043c:<\/p>\n<pre><code class=\"go\">type Claims struct {    Phone string `json:\"phone\"`    Type  string `json:\"type\"` \/\/ \"access\" \u0438\u043b\u0438 \"refresh\"    jwt.RegisteredClaims}func generateAccessToken(phone string) (string, error) {    claims := &amp;Claims{        Phone: phone,        Type:  \"access\",        RegisteredClaims: jwt.RegisteredClaims{            \/\/ 15 \u043c\u0438\u043d\u0443\u0442 \u0432 \u043f\u0440\u043e\u0434\u0435; \u0434\u043b\u044f \u0434\u0435\u043c\u043e \u043f\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 1 \u043c\u0438\u043d\u0443\u0442\u0443,            \/\/ \u0447\u0442\u043e\u0431\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0443 \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u0447\u0438\u043a\u0430 \u043d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0435            ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),        },    }    return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(jwtKey)}func generateRefreshToken(phone string) (string, error) {    claims := &amp;Claims{        Phone: phone,        Type:  \"refresh\",        RegisteredClaims: jwt.RegisteredClaims{            ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),        },    }    return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(refreshTokenKey)}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f<\/h4>\n<p>\u0413\u043b\u0443\u0431\u043e\u043a\u043e \u0437\u0430\u043a\u0430\u043f\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0435\u00a0\u0431\u0443\u0434\u0435\u043c: \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c username \u0438 phone, \u0445\u044d\u0448\u0438\u0440\u0443\u0435\u043c \u043f\u0430\u0440\u043e\u043b\u044c bcrypt \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c:<\/p>\n<pre><code class=\"go\">func registerHandler(w http.ResponseWriter, r *http.Request) {    var user User    if err := json.NewDecoder(r.Body).Decode(&amp;user); err != nil {        writeError(w, err.Error(), http.StatusBadRequest)        return    }    var existing string    if db.QueryRow(\"SELECT username FROM users WHERE username=$1\", user.Username).Scan(&amp;existing) == nil {        writeError(w, \"\u041d\u0438\u043a\u043d\u0435\u0439\u043c \u0437\u0430\u043d\u044f\u0442\", http.StatusConflict)        return    }    if db.QueryRow(\"SELECT phone FROM users WHERE phone=$1\", user.Phone).Scan(&amp;existing) == nil {        writeError(w, \"\u0422\u0435\u043b\u0435\u0444\u043e\u043d \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\", http.StatusConflict)        return    }    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)    if err != nil {        writeError(w, err.Error(), http.StatusInternalServerError)        return    }    _, err = db.Exec(        \"INSERT INTO users (username, phone, name, lastname, password, icons) VALUES ($1,$2,$3,$4,$5,$6)\",        user.Username, user.Phone, user.Name, user.Lastname, hashedPassword, user.Icons,    )    if err != nil {        writeError(w, err.Error(), http.StatusInternalServerError)        return    }    w.Header().Set(\"Content-Type\", \"application\/json\")    json.NewEncoder(w).Encode(map[string]string{\"message\": \"\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0448\u043b\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e!\"})}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u041b\u043e\u0433\u0438\u043d<\/h4>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0442\u0435\u043b\u0435\u0444\u043e\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c, \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u043f\u0430\u0440\u0443 \u0442\u043e\u043a\u0435\u043d\u043e\u0432. Refresh \u0441\u0442\u0430\u0432\u0438\u043c \u0432\u00a0\u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u0443\u044e cookie, access \u043e\u0442\u0434\u0430\u0451\u043c \u0432\u00a0\u0442\u0435\u043b\u0435 \u043e\u0442\u0432\u0435\u0442\u0430:<\/p>\n<pre><code class=\"go\">func loginHandler(w http.ResponseWriter, r *http.Request) {    var user User    if err := json.NewDecoder(r.Body).Decode(&amp;user); err != nil {        writeError(w, err.Error(), http.StatusBadRequest)        return    }    var storedPassword string    err := db.QueryRow(\"SELECT password FROM users WHERE phone=$1\", user.Phone).Scan(&amp;storedPassword)    if err != nil || bcrypt.CompareHashAndPassword([]byte(storedPassword), []byte(user.Password)) != nil {        writeError(w, \"\u041b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u043d\u0435 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u044e\u0442\", http.StatusUnauthorized)        return    }    accessToken, err := generateAccessToken(user.Phone)    if err != nil {        writeError(w, err.Error(), http.StatusInternalServerError)        return    }    refreshToken, err := generateRefreshToken(user.Phone)    if err != nil {        writeError(w, err.Error(), http.StatusInternalServerError)        return    }    \/\/ Refresh \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u0443\u044e cookie    http.SetCookie(w, &amp;http.Cookie{        Name:     \"refresh_token\",        Value:    refreshToken,        Path:     \"\/\",        HttpOnly: true,           \/\/ JS \u043d\u0435 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0435\u0442 cookie \u2014 \u0437\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 \u043a\u0440\u0430\u0436\u0438        Secure:   secureCookies,  \/\/ true \u0432 \u043f\u0440\u043e\u0434\u0435: cookie \u0445\u043e\u0434\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e HTTPS        SameSite: http.SameSiteStrictMode, \/\/ cookie \u043d\u0435 \u0443\u0439\u0434\u0451\u0442 \u0441 \u0447\u0443\u0436\u043e\u0433\u043e \u0441\u0430\u0439\u0442\u0430 \u2014 \u0437\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 CSRF    })    w.Header().Set(\"Content-Type\", \"application\/json\")    json.NewEncoder(w).Encode(map[string]string{\"access_token\": accessToken})}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0440\u0438 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430 cookie \u043f\u0440\u043e\u0441\u0442\u044b\u043c\u0438 \u0441\u043b\u043e\u0432\u0430\u043c\u0438:<\/p>\n<ul>\n<li>\n<p><code>HttpOnly<\/code>\u00a0\u2014 JavaScript \u043d\u0435\u00a0\u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c cookie, \u0435\u0451 \u043c\u043e\u0436\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u0435\u0440\u0435\u0441\u044b\u043b\u0430\u0442\u044c \u0441\u00a0\u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438. \u0418\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u043e \u043d\u0430\u043c \u0438 \u043d\u0443\u0436\u043d\u043e.<\/p>\n<\/li>\n<li>\n<p><code>Secure<\/code>\u00a0\u2014 cookie \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u00a0HTTPS. \u041b\u043e\u043a\u0430\u043b\u044c\u043d\u043e \u043c\u043e\u0436\u043d\u043e \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u043c, \u043f\u0440\u0438\u00a0\u0434\u0435\u043f\u043b\u043e\u0435\u00a0\u2014 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c.<\/p>\n<\/li>\n<li>\n<p><code>SameSite=Strict<\/code>\u00a0\u2014 \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043d\u0435\u00a0\u043f\u0440\u0438\u043b\u043e\u0436\u0438\u0442 cookie, \u0435\u0441\u043b\u0438 \u0437\u0430\u043f\u0440\u043e\u0441 \u0438\u0434\u0451\u0442 \u0441\u00a0\u0447\u0443\u0436\u043e\u0433\u043e \u0441\u0430\u0439\u0442\u0430.<\/p>\n<\/li>\n<\/ul>\n<h4>\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u043e\u0432<\/h4>\n<p>Access \u0436\u0438\u0432\u0451\u0442 \u043c\u0438\u043d\u0443\u0442\u044b. \u0411\u0435\u0437\u00a0\u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043e\u0447\u0435\u043d\u044c\u00a0\u0431\u044b\u0441\u0442\u0440\u043e \u043d\u0430\u0447\u043d\u0451\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c 401. <code>\/refresh<\/code> \u0447\u0438\u0442\u0430\u0435\u0442 cookie, \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u0442 refresh \u0438 \u0432\u044b\u0434\u0430\u0451\u0442 \u043d\u043e\u0432\u0443\u044e \u043f\u0430\u0440\u0443 (\u0440\u043e\u0442\u0430\u0446\u0438\u044f):<\/p>\n<pre><code class=\"go\">func refreshHandler(w http.ResponseWriter, r *http.Request) {    cookie, err := r.Cookie(\"refresh_token\")    if err != nil {        writeError(w, \"Refresh token \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442\", http.StatusUnauthorized)        return    }    claims := &amp;Claims{}    tkn, err := jwt.ParseWithClaims(cookie.Value, claims,        func(t *jwt.Token) (interface{}, error) { return refreshTokenKey, nil },        jwt.WithValidMethods([]string{\"HS256\"}),    )    if err != nil || !tkn.Valid || claims.Type != \"refresh\" {        writeError(w, \"\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 refresh token\", http.StatusUnauthorized)        return    }    newAccess, err := generateAccessToken(claims.Phone)    if err != nil {        writeError(w, err.Error(), http.StatusInternalServerError)        return    }    newRefresh, err := generateRefreshToken(claims.Phone)    if err != nil {        writeError(w, err.Error(), http.StatusInternalServerError)        return    }    http.SetCookie(w, &amp;http.Cookie{        Name: \"refresh_token\", Value: newRefresh, Path: \"\/\",        HttpOnly: true, Secure: secureCookies, SameSite: http.SameSiteStrictMode,    })    w.Header().Set(\"Content-Type\", \"application\/json\")    json.NewEncoder(w).Encode(map[string]string{\"access_token\": newAccess})}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u041b\u043e\u0433\u0430\u0443\u0442<\/h4>\n<p>\u0424\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u043d\u0435\u00a0\u043c\u043e\u0436\u0435\u0442 \u0443\u0434\u0430\u043b\u0438\u0442\u044c HttpOnly\u2011cookie \u0441\u0430\u043c\u00a0\u2014 \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u0435\u0451 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0443\u0434\u0430\u043b\u044f\u0435\u0442 cookie \u0431\u0435\u043a\u0435\u043d\u0434:<\/p>\n<pre><code class=\"go\">func logoutHandler(w http.ResponseWriter, r *http.Request) {    http.SetCookie(w, &amp;http.Cookie{        Name: \"refresh_token\", Value: \"\", Path: \"\/\",        HttpOnly: true, MaxAge: -1, SameSite: http.SameSiteStrictMode,    })    w.Header().Set(\"Content-Type\", \"application\/json\")    json.NewEncoder(w).Encode(map[string]string{\"message\": \"\u0412\u044b \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0440\u0430\u0437\u043b\u043e\u0433\u0438\u043d\u0438\u043b\u0438\u0441\u044c!\"})}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>Middleware \u0434\u043b\u044f\u00a0\u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u044b\u0445 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u0438 \u043f\u0440\u043e\u0444\u0438\u043b\u044c<\/h4>\n<p>\u0422\u043e\u043a\u0435\u043d \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u043c \u043f\u043e\u00a0\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u0443\u00a0\u2014 \u0441\u00a0\u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c <code>Bearer<\/code>:<\/p>\n<pre><code class=\"go\">func tokenFromRequest(r *http.Request) string {    return strings.TrimPrefix(r.Header.Get(\"Authorization\"), \"Bearer \")}func validateToken(next http.Handler) http.Handler {    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {        claims := &amp;Claims{}        tkn, err := jwt.ParseWithClaims(tokenFromRequest(r), claims,            func(t *jwt.Token) (interface{}, error) { return jwtKey, nil },            jwt.WithValidMethods([]string{\"HS256\"}),        )        if err != nil || !tkn.Valid || claims.Type != \"access\" {            writeError(w, \"Invalid token\", http.StatusUnauthorized)            return        }        next.ServeHTTP(w, r)    })}\/\/ \u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u044b\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043afunc profileHandler(w http.ResponseWriter, r *http.Request) {    claims := &amp;Claims{}    jwt.ParseWithClaims(tokenFromRequest(r), claims,        func(t *jwt.Token) (interface{}, error) { return jwtKey, nil },        jwt.WithValidMethods([]string{\"HS256\"}),    )    var user User    err := db.QueryRow(        \"SELECT id, username, phone, name, lastname, icons FROM users WHERE phone=$1\", claims.Phone,    ).Scan(&amp;user.ID, &amp;user.Username, &amp;user.Phone, &amp;user.Name, &amp;user.Lastname, &amp;user.Icons)    if err != nil {        writeError(w, \"User not found\", http.StatusNotFound)        return    }    w.Header().Set(\"Content-Type\", \"application\/json\")    json.NewEncoder(w).Encode(user)}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b<\/h4>\n<pre><code class=\"go\">func main() {    initDB()    defer db.Close()    r := mux.NewRouter()    r.HandleFunc(\"\/register\", registerHandler).Methods(\"POST\")    r.HandleFunc(\"\/login\", loginHandler).Methods(\"POST\")    r.HandleFunc(\"\/refresh\", refreshHandler).Methods(\"POST\")    r.HandleFunc(\"\/logout\", logoutHandler).Methods(\"POST\")    r.Handle(\"\/profile\", validateToken(http.HandlerFunc(profileHandler))).Methods(\"GET\")    c := cors.New(cors.Options{        AllowedOrigins:   []string{\"http:\/\/localhost:5173\"},        AllowCredentials: true, \/\/ \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u0447\u0442\u043e\u0431\u044b \u0445\u043e\u0434\u0438\u043b\u0438 cookie        AllowedHeaders:   []string{\"Authorization\", \"Content-Type\"},        AllowedMethods:   []string{\"GET\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\"},    })    log.Println(\"\u0421\u0435\u0440\u0432\u0435\u0440 \u0437\u0430\u043f\u0443\u0449\u0435\u043d \u043d\u0430 :8080\")    log.Fatal(http.ListenAndServe(\":8080\", c.Handler(r)))}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0430\u0440\u0430 \u0441\u043b\u043e\u0432 \u043f\u0440\u043e\u00a0CORS. \u0411\u0440\u0430\u0443\u0437\u0435\u0440 \u043f\u043e\u00a0\u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0437\u0430\u043f\u0440\u0435\u0449\u0430\u0435\u0442 \u0444\u0440\u043e\u043d\u0442\u0443 \u043d\u0430 <code>localhost:5173<\/code> \u0434\u0435\u043b\u0430\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043d\u0430\u00a0\u0431\u0435\u043a\u0435\u043d\u0434 \u043d\u0430 <code>localhost:8080<\/code>\u00a0\u2014 \u044d\u0442\u043e \u0440\u0430\u0437\u043d\u044b\u0435 origin. \u0412\u00a0\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u043c\u044b \u044f\u0432\u043d\u043e \u0440\u0430\u0437\u0440\u0435\u0448\u0430\u0435\u043c origin \u0444\u0440\u043e\u043d\u0442\u0430 \u0438 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u043c <code>AllowCredentials<\/code>\u00a0\u2014 \u0431\u0435\u0437\u00a0\u044d\u0442\u043e\u0433\u043e cookie \u0441\u00a0refresh \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435\u00a0\u0431\u0443\u0434\u0443\u0442 \u0443\u0445\u043e\u0434\u0438\u0442\u044c.<\/p>\n<h3>\u0424\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u043d\u0430\u00a0Vue 3<\/h3>\n<h4>Store: access\u2011token \u0436\u0438\u0432\u0451\u0442 \u0432\u00a0\u043f\u0430\u043c\u044f\u0442\u0438<\/h4>\n<pre><code>\/\/ store\/store.tsimport { createStore } from 'vuex'import { refreshApi } from '@\/api\/api'export default createStore({  state: { accessToken: null as string | null },  mutations: {    setAccessToken(state, token: string) { state.accessToken = token },    clearAccessToken(state) { state.accessToken = null },  },  actions: {    async refreshToken({ commit }) {      try {        const newToken = await refreshApi()        commit('setAccessToken', newToken)      } catch (error) {        console.error('\u041e\u0448\u0438\u0431\u043a\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430:', error)        commit('clearAccessToken')        localStorage.removeItem('isAuthenticated')      }    },  },  getters: { accessToken: (s) =&gt; s.accessToken },})<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412\u00a0localStorage \u043f\u0440\u0438\u00a0\u044d\u0442\u043e\u043c \u043a\u043b\u0430\u0434\u0451\u043c <strong>\u043d\u0435\u00a0\u0442\u043e\u043a\u0435\u043d<\/strong>, \u0430\u00a0\u0431\u0435\u0437\u043e\u0431\u0438\u0434\u043d\u044b\u0439 \u0444\u043b\u0430\u0433 <code>isAuthenticated<\/code>\u00a0\u2014 \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u0442\u043c\u0435\u0442\u043a\u0443 \u00ab\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043b\u043e\u0433\u0438\u043d\u0438\u043b\u0441\u044f\u00bb, \u043f\u043e\u00a0\u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0440\u043e\u0443\u0442\u0435\u0440 \u043f\u043e\u043d\u0438\u043c\u0430\u0435\u0442, \u043f\u044b\u0442\u0430\u0442\u044c\u0441\u044f\u00a0\u043b\u0438 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e.<\/p>\n<h4>\u041b\u043e\u0433\u0438\u043d<\/h4>\n<p>\u0412\u0441\u044f \u0441\u0443\u0442\u044c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043b\u043e\u0433\u0438\u043d\u0430\u00a0\u2014 \u0432\u00a0\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0444\u043e\u0440\u043c\u044b:<\/p>\n<pre><code>async handleSubmit() {  const userData = { phone: this.phone, password: this.password }  try {    const res = await loginApi(userData)    this.$store.commit('setAccessToken', res.access_token) \/\/ \u0442\u043e\u043a\u0435\u043d \u2014 \u0432 \u043f\u0430\u043c\u044f\u0442\u044c    localStorage.setItem('isAuthenticated', 'true')        \/\/ \u0444\u043b\u0430\u0433 \u2014 \u0432 localStorage    this.$router.push({ name: 'ChatList' })  } catch (error) {    toast.error(error.message)  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u0435\u0451 \u0440\u0435\u0448\u0435\u043d\u0438\u0435<\/h4>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0441\u0442\u043e\u043b\u043a\u043d\u0451\u043c\u0441\u044f \u0441\u00a0\u0434\u0432\u0443\u043c\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430\u043c\u0438. \u0412\u043e\u2011\u043f\u0435\u0440\u0432\u044b\u0445, access \u0436\u0438\u0432\u0451\u0442 \u0432\u0441\u0435\u0433\u043e \u043c\u0438\u043d\u0443\u0442\u0443\u00a0\u2014 \u0437\u043d\u0430\u0447\u0438\u0442, \u043d\u0443\u0436\u0435\u043d <code>\/refresh<\/code> (\u043c\u044b \u0435\u0433\u043e \u0443\u0436\u0435 \u0441\u0434\u0435\u043b\u0430\u043b\u0438). \u0412\u043e\u2011\u0432\u0442\u043e\u0440\u044b\u0445, \u043f\u0440\u0438\u00a0\u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b store \u043e\u0447\u0438\u0449\u0430\u0435\u0442\u0441\u044f, \u0438 access\u2011token \u043f\u0440\u043e\u043f\u0430\u0434\u0430\u0435\u0442. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u00a0\u0431\u044b\u043b \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u00a0\u2014 \u0438 \u0432\u0434\u0440\u0443\u0433 401.<\/p>\n<p>\u0420\u0435\u0448\u0430\u0435\u043c \u0447\u0435\u0440\u0435\u0437 \u0444\u043b\u0430\u0433: \u0435\u0441\u043b\u0438 \u0444\u043b\u0430\u0433 \u0441\u0442\u043e\u0438\u0442, \u0430\u00a0\u0442\u043e\u043a\u0435\u043d\u0430 \u0432\u00a0\u043f\u0430\u043c\u044f\u0442\u0438 \u043d\u0435\u0442\u00a0\u2014 \u0437\u043d\u0430\u0447\u0438\u0442, \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u043b\u0438, \u0438 \u0441\u0435\u0441\u0441\u0438\u044e \u043d\u0430\u0434\u043e \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 refresh:<\/p>\n<pre><code>\/\/ router.tsrouter.beforeEach(async (to, from, next) =&gt; {  const publicPages = ['Login', 'Register']  const isPublic = publicPages.includes(to.name as string)  const isAuth = localStorage.getItem('isAuthenticated') === 'true'  if (!isAuth &amp;&amp; !isPublic) return next({ name: 'Login' })  \/\/ \u0422\u043e\u043a\u0435\u043d\u0430 \u0432 \u043f\u0430\u043c\u044f\u0442\u0438 \u043d\u0435\u0442, \u043d\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043b\u043e\u0433\u0438\u043d\u0438\u043b\u0441\u044f \u2014 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e.  \/\/ \u0414\u0435\u043b\u0430\u0435\u043c \u044d\u0442\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0433\u0434\u0430 \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0435\u0442, \u0430 \u043d\u0435 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0439 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438.  if (isAuth &amp;&amp; !isPublic &amp;&amp; !store.getters.accessToken) {    try {      await store.dispatch('refreshToken')    } catch {      return next({ name: 'Login' })    }  }  next()})<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u00ab\u043e\u0442\u043e\u0448\u0451\u043b \u043f\u043e\u043f\u0438\u0442\u044c \u0447\u0430\u0439\u00bb<\/h4>\n<p>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u043a\u043b\u0430\u0434\u043a\u0443 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 \u0434\u043e\u043b\u044c\u0448\u0435 \u0436\u0438\u0437\u043d\u0438 access\u2011\u0442\u043e\u043a\u0435\u043d\u0430 \u0438 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c\u00a0\u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043c\u0435\u043d\u044f\u0442\u044c \u0430\u0432\u0430\u0442\u0430\u0440\u043a\u0443. \u0427\u0442\u043e\u0431\u044b \u043d\u0435\u00a0\u0432\u044b\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u0441 401\u00a0\u043f\u043e\u0441\u0440\u0435\u0434\u0438 \u0440\u0430\u0431\u043e\u0442\u044b, \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u043e\u0442\u0432\u0435\u0442 axios \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441 \u0441\u00a0\u043d\u043e\u0432\u044b\u043c \u0442\u043e\u043a\u0435\u043d\u043e\u043c:<\/p>\n<pre><code>\/\/ services\/apiClient.tsimport axios from 'axios'import store from '@\/store\/store'const apiClient = axios.create({  baseURL: 'http:\/\/localhost:8080',  timeout: 10000,  withCredentials: true, \/\/ \u0447\u0442\u043e\u0431\u044b \u0443\u0445\u043e\u0434\u0438\u043b\u0430 cookie \u0441 refresh})\/\/ \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0442\u043e\u043a\u0435\u043dapiClient.interceptors.request.use((config) =&gt; {  const token = store.getters.accessToken  if (token) config.headers.Authorization = `Bearer ${token}`  return config})\/\/ \u041f\u0440\u0438 401 \u2014 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0442\u043e\u043a\u0435\u043d\u044b \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441 \u043e\u0434\u0438\u043d \u0440\u0430\u0437apiClient.interceptors.response.use(  (response) =&gt; response,  async (error) =&gt; {    const original = error.config    if (error.response?.status === 401 &amp;&amp; !original._retry) {      original._retry = true      try {        await store.dispatch('refreshToken')        return apiClient.request(original)      } catch (refreshError) {        return Promise.reject(refreshError)      }    }    if (error.response?.data?.result) {      return Promise.reject({ message: error.response.data.result })    }    return Promise.reject(error.message || '\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430')  })export default apiClient<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0424\u043b\u0430\u0433 <code>_retry<\/code> \u0437\u0430\u0449\u0438\u0449\u0430\u0435\u0442 \u043e\u0442\u00a0\u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u043e\u0433\u043e \u0446\u0438\u043a\u043b\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439, \u0435\u0441\u043b\u0438 refresh \u0442\u043e\u0436\u0435 \u043f\u0440\u043e\u0442\u0443\u0445. \u0414\u043b\u044f\u00a0\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432\u0441\u0451 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u043d\u0435\u0437\u0430\u043c\u0435\u0442\u043d\u043e: \u0437\u0430\u043f\u0440\u043e\u0441 \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u00ab\u0444\u0435\u0439\u043b\u0438\u0442\u0441\u044f\u00bb, interceptor \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u0442\u043e\u043a\u0435\u043d\u044b \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u0442 \u0435\u0433\u043e \u0443\u0436\u0435 \u0441\u00a0\u0432\u0430\u043b\u0438\u0434\u043d\u044b\u043c access.<\/p>\n<p>\u041d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442 \u0442\u0430\u0439\u043c\u0435\u0440 \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u044e\u0442 \u0442\u043e\u043a\u0435\u043d\u044b \u043f\u0440\u0435\u0432\u0435\u043d\u0442\u0438\u0432\u043d\u043e \u043a\u0430\u0436\u0434\u044b\u0435 N \u043c\u0438\u043d\u0443\u0442. \u041c\u043d\u0435 \u0442\u0430\u043a\u043e\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u043a\u0430\u0436\u0435\u0442\u0441\u044f\u00a0\u043b\u0438\u0448\u043d\u0435\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439: \u043f\u0440\u0438\u00a0\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 \u0442\u0430\u0439\u043c\u0435\u0440 \u043a\u0440\u0443\u0442\u0438\u0442\u0441\u044f \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e, \u0430\u00a0\u0444\u043e\u0440\u043c\u0430\u0442 \u0441\u00a0\u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u043e\u0433\u0434\u0430, \u043a\u043e\u0433\u0434\u0430 \u044d\u0442\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0443\u0436\u043d\u043e.<\/p>\n<h4>API\u2011\u0441\u043b\u043e\u0439<\/h4>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435: <code>\/refresh<\/code> \u0434\u0451\u0440\u0433\u0430\u0435\u043c \u00ab\u0433\u043e\u043b\u044b\u043c\u00bb fetch, \u0430\u00a0\u043d\u0435\u00a0apiClient\u00a0\u2014 \u0438\u043d\u0430\u0447\u0435 \u043f\u043e\u043f\u0430\u0434\u0451\u043c \u0432\u00a0\u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 interceptor \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0446\u0438\u043a\u043b:<\/p>\n<pre><code>\/\/ api\/api.tsconst loginApi = async (data: LoginData) =&gt; {  const response = await apiClient.post('\/login', data, {    headers: { 'Content-Type': 'application\/json' },  })  return response.data}const refreshApi = async () =&gt; {  const response = await fetch('http:\/\/localhost:8080\/refresh', {    method: 'POST',    credentials: 'include', \/\/ \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c cookie \u0441 refresh  })  if (!response.ok) throw new Error('\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d')  const data = await response.json()  return data.access_token}const profileApi = async () =&gt; (await apiClient.get('\/profile')).dataconst logoutApi = async () =&gt; (await apiClient.post('\/logout')).data<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u00a0\u044d\u0442\u043e\u043c\u0443 \u043c\u043e\u043c\u0435\u043d\u0442\u0443 \u0441\u0435\u0441\u0441\u0438\u044f \u0436\u0438\u0432\u0451\u0442 \u0441\u0440\u0430\u0437\u0443 \u0432\u00a0\u0442\u0440\u0451\u0445 \u043c\u0435\u0441\u0442\u0430\u0445: cookie \u0441\u00a0refresh (\u0431\u0435\u043a\u0435\u043d\u0434), access\u2011token \u0432\u00a0store (\u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434) \u0438 \u0444\u043b\u0430\u0433 <code>isAuthenticated<\/code> \u0432\u00a0localStorage (\u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434). \u0427\u0442\u043e\u0431\u044b \u0440\u0430\u0437\u043b\u043e\u0433\u0438\u043d\u0438\u0442\u044c\u0441\u044f \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e, \u0432\u044b\u0447\u0438\u0449\u0430\u0435\u043c \u0432\u0441\u0435 \u0442\u0440\u0438:<\/p>\n<ol>\n<li>\n<p>\u0412\u044b\u0437\u044b\u0432\u0430\u0435\u043c <code>logoutApi()<\/code>\u00a0\u2014 \u0431\u0435\u043a\u0435\u043d\u0434 \u0443\u0434\u0430\u043b\u044f\u0435\u0442 HttpOnly\u2011cookie; \u0441\u0430\u043c \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435\u00a0\u043c\u043e\u0436\u0435\u0442.<\/p>\n<\/li>\n<li>\n<p><code>commit('clearAccessToken')<\/code>\u00a0\u2014 \u043e\u0447\u0438\u0449\u0430\u0435\u043c access\u2011token \u0432\u00a0store.<\/p>\n<\/li>\n<li>\n<p><code>localStorage.removeItem('isAuthenticated')<\/code>\u00a0\u2014 \u0443\u0431\u0438\u0440\u0430\u0435\u043c \u0444\u043b\u0430\u0433, \u0447\u0442\u043e\u0431\u044b \u0440\u043e\u0443\u0442\u0435\u0440 \u043f\u0440\u0438\u00a0\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0435 \u043d\u0435\u00a0\u043f\u044b\u0442\u0430\u043b\u0441\u044f \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u0447\u0435\u0440\u0435\u0437 <code>\/refresh<\/code>.<\/p>\n<\/li>\n<\/ol>\n<p>\u041f\u0430\u0440\u0430 \u0441\u043b\u043e\u0432 \u0434\u043b\u044f\u00a0\u0442\u0435\u0445, \u043a\u0442\u043e \u0441\u00a0Vuex \u043d\u0435\u00a0\u0441\u0442\u0430\u043b\u043a\u0438\u0432\u0430\u043b\u0441\u044f: <code>commit<\/code>\u00a0\u2014 \u044d\u0442\u043e \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 Vuex \u0434\u043b\u044f\u00a0\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f store. State \u043d\u0435\u00a0\u043c\u0435\u043d\u044f\u044e\u0442 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0438\u0437\u00a0\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430: \u0432\u00a0store \u043e\u0431\u044a\u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f <strong>\u043c\u0443\u0442\u0430\u0446\u0438\u0438<\/strong>\u00a0\u2014 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435,\u00a0\u2014 \u0430\u00a0\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u0438\u0445 \u043f\u043e\u00a0\u0438\u043c\u0435\u043d\u0438 \u0447\u0435\u0440\u0435\u0437 <code>commit<\/code>:<\/p>\n<pre><code>\/\/ \u0432 store.ts \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0430 \u043c\u0443\u0442\u0430\u0446\u0438\u044fmutations: {  clearAccessToken(state) { state.accessToken = null },}\/\/ \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u0435\u0451this.$store.commit('clearAccessToken')<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0435\u00a0\u043f\u0443\u0442\u0430\u0439\u0442\u0435 \u0441 <code>dispatch<\/code>\u00a0\u2014 \u0442\u043e\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 <strong>actions<\/strong>, \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u00a0\u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438 \u043a\u00a0\u0441\u0435\u0440\u0432\u0435\u0440\u0443 (\u043a\u0430\u043a \u043d\u0430\u0448 <code>refreshToken<\/code>). \u041f\u0440\u0438\u00a0\u043b\u043e\u0433\u0430\u0443\u0442\u0435 \u043d\u0430\u043c \u043d\u0435\u00a0\u043d\u0443\u0436\u043d\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0433\u043e \u0432\u00a0\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438\u00a0\u2014 \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u0431\u043d\u0443\u043b\u0438\u0442\u044c \u043f\u043e\u043b\u0435\u00a0\u2014 \u043f\u043e\u044d\u0442\u043e\u043c\u0443 <code>commit<\/code>, \u0430\u00a0\u043d\u0435 <code>dispatch<\/code>.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043b\u044e\u0431\u043e\u0439 \u0438\u0437\u00a0\u0442\u0440\u0451\u0445 \u043f\u0443\u043d\u043a\u0442\u043e\u0432\u00a0\u2014 \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u00ab\u0437\u043e\u043c\u0431\u0438\u2011\u0441\u0435\u0441\u0441\u0438\u044e\u00bb:\u00a0\u043b\u0438\u0431\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0442 \u0445\u043e\u0434\u0438\u0442\u044c \u0441\u043e \u0441\u0442\u0430\u0440\u044b\u043c \u0442\u043e\u043a\u0435\u043d\u043e\u043c \u0434\u043e\u00a0\u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b,\u00a0\u043b\u0438\u0431\u043e \u0440\u043e\u0443\u0442\u0435\u0440 \u043f\u043e\u043f\u044b\u0442\u0430\u0435\u0442\u0441\u044f \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u043f\u0440\u0438\u00a0\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u043a\u043b\u0438\u043a\u0435. \u0412\u044b\u0447\u0438\u0441\u0442\u0438\u043b\u0438 \u0432\u0441\u0451\u00a0\u2014 \u0440\u0435\u0434\u0438\u0440\u0435\u043a\u0442\u0438\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0430\u00a0\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043b\u043e\u0433\u0438\u043d\u0430.<\/p>\n<p>\u0418\u0442\u043e\u0433\u043e\u0432\u0430\u044f \u0441\u0445\u0435\u043c\u0430 \u0446\u0435\u043b\u0438\u043a\u043e\u043c: \u043b\u043e\u0433\u0438\u043d\u0438\u043c\u0441\u044f \u2192 \u0434\u0435\u0440\u0436\u0438\u043c access \u0432\u00a0store \u2192 \u043f\u0440\u0438\u00a0\u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0435\u0433\u043e \u0447\u0435\u0440\u0435\u0437 <code>\/refresh<\/code> \u043f\u043e\u00a0\u0444\u043b\u0430\u0433\u0443 \u2192 \u043f\u0440\u0438 401\u00a0\u0432\u043e\u00a0\u0432\u0440\u0435\u043c\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043c\u043e\u043b\u0447\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c\u0441\u044f \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441 \u2192 \u043f\u0440\u0438\u00a0\u043b\u043e\u0433\u0430\u0443\u0442\u0435 \u0431\u0435\u043a\u0435\u043d\u0434 \u0443\u0434\u0430\u043b\u044f\u0435\u0442 cookie, \u0444\u0440\u043e\u043d\u0442 \u0447\u0438\u0441\u0442\u0438\u0442 store \u0438 \u0444\u043b\u0430\u0433.<\/p>\n<h3>\u041f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u043e \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0435\u0435, \u0447\u0435\u043c localStorage<\/h3>\n<p>\u0414\u0432\u0435 \u0433\u043b\u0430\u0432\u043d\u044b\u0435 \u0443\u0433\u0440\u043e\u0437\u044b, \u043e\u00a0\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0441\u0442\u043e\u0438\u0442 \u043f\u043e\u043c\u043d\u0438\u0442\u044c:<\/p>\n<ul>\n<li>\n<p><strong>XSS<\/strong>\u00a0\u2014 \u0432\u0440\u0435\u0434\u043e\u043d\u043e\u0441\u043d\u044b\u0439 \u0441\u043a\u0440\u0438\u043f\u0442 \u043f\u043e\u043f\u0430\u0434\u0430\u0435\u0442 \u043d\u0430\u00a0\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0447\u0435\u0440\u0435\u0437 \u043d\u0435\u044d\u043a\u0440\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439). \u0415\u0441\u043b\u0438 \u0442\u043e\u043a\u0435\u043d \u043b\u0435\u0436\u0438\u0442 \u0432\u00a0localStorage, \u0441\u043a\u0440\u0438\u043f\u0442 \u0435\u0433\u043e \u0447\u0438\u0442\u0430\u0435\u0442 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0430\u0442\u0430\u043a\u0443\u044e\u0449\u0435\u043c\u0443. \u0415\u0441\u043b\u0438 \u044d\u0442\u043e refresh\u00a0\u2014 \u0437\u043b\u043e\u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u0438\u043a \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f \u043d\u0430\u00a0\u043c\u0435\u0441\u044f\u0446\u044b. HttpOnly\u2011cookie \u0442\u0430\u043a \u043d\u0435\u00a0\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c.<\/p>\n<\/li>\n<li>\n<p><strong>CSRF<\/strong>\u00a0\u2014 \u0447\u0443\u0436\u043e\u0439 \u0441\u0430\u0439\u0442 \u0434\u0435\u043b\u0430\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441 \u043a\u00a0\u0432\u0430\u0448\u0435\u043c\u0443 \u0431\u0435\u043a\u0435\u043d\u0434\u0443 \u043e\u0442\u00a0\u0438\u043c\u0435\u043d\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f: \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043f\u0440\u0438\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442 cookie \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438. \u041e\u0442\u00a0\u044d\u0442\u043e\u0433\u043e \u043d\u0430\u0441 \u0441\u0442\u0440\u0430\u0445\u0443\u0435\u0442 <code>SameSite=Strict<\/code>\u00a0\u2014 \u0441\u00a0\u0447\u0443\u0436\u043e\u0433\u043e \u0441\u0430\u0439\u0442\u0430 cookie \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435\u00a0\u0443\u0439\u0434\u0451\u0442.<\/p>\n<\/li>\n<\/ul>\n<p>\u0427\u0435\u0441\u0442\u043d\u043e\u0435 \u0437\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u0435: \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 access \u0432\u00a0\u043f\u0430\u043c\u044f\u0442\u0438\u00a0\u2014 \u043d\u0435\u00a0\u0441\u0435\u0440\u0435\u0431\u0440\u044f\u043d\u0430\u044f \u043f\u0443\u043b\u044f \u043e\u0442\u00a0XSS (\u0441\u043a\u0440\u0438\u043f\u0442, \u043e\u043a\u0430\u0437\u0430\u0432\u0448\u0438\u0439\u0441\u044f \u043d\u0430\u00a0\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435, \u0442\u0435\u043e\u0440\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043c\u043e\u0436\u0435\u0442 \u0434\u043e\u0442\u044f\u043d\u0443\u0442\u044c\u0441\u044f \u0434\u043e\u00a0store). \u0421\u043c\u044b\u0441\u043b \u0432\u00a0\u0442\u043e\u043c, \u0447\u0442\u043e\u00a0\u0442\u043e\u043a\u0435\u043d \u043d\u0435\u00a0\u043b\u0435\u0436\u0438\u0442 \u0432\u00a0\u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e\u043c \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0438 \u0443\u043c\u0438\u0440\u0430\u0435\u0442 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441\u00a0\u0432\u043a\u043b\u0430\u0434\u043a\u043e\u0439, \u0430\u00a0\u0433\u043b\u0430\u0432\u043d\u0430\u044f \u0437\u0430\u0449\u0438\u0442\u0430\u00a0\u2014 \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0439 TTL access\u2011\u0442\u043e\u043a\u0435\u043d\u0430 \u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 XSS \u043a\u0430\u043a\u00a0\u0442\u0430\u043a\u043e\u0432\u043e\u0433\u043e.<\/p>\n<h3>\u0427\u0442\u043e\u00a0\u0441\u0442\u043e\u0438\u0442 \u0443\u0447\u0435\u0441\u0442\u044c \u0432\u00a0\u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435<\/h3>\n<p>\u041d\u0430\u0448\u0430 \u0441\u0445\u0435\u043c\u0430 \u0445\u043e\u0440\u043e\u0448\u0430 \u0434\u043b\u044f\u00a0\u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f, \u043d\u043e\u00a0\u0432\u00a0\u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043d\u0443\u0436\u043d\u043e \u0443\u0447\u0435\u0441\u0442\u044c \u0435\u0449\u0451 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u043e\u043c\u0435\u043d\u0442\u043e\u0432. \u041e\u0431\u044a\u044f\u0441\u043d\u044e \u043a\u0430\u0436\u0434\u044b\u0439 \u043f\u0440\u043e\u0441\u0442\u044b\u043c\u0438 \u0441\u043b\u043e\u0432\u0430\u043c\u0438.<\/p>\n<p><strong>\u0422\u043e\u043a\u0435\u043d \u043d\u0435\u043b\u044c\u0437\u044f \u00ab\u043e\u0442\u043e\u0437\u0432\u0430\u0442\u044c\u00bb \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e.<\/strong> JWT\u00a0\u2014 \u043a\u0430\u043a\u00a0\u0431\u0443\u043c\u0430\u0436\u043d\u044b\u0439 \u0431\u0438\u043b\u0435\u0442: \u0435\u0433\u043e \u0432\u044b\u043f\u0443\u0441\u0442\u0438\u043b\u0438, \u0438 \u0434\u043e\u00a0\u043a\u043e\u043d\u0446\u0430 \u0441\u0440\u043e\u043a\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043e\u043d \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043a\u043d\u043e\u043f\u043a\u0438 \u00ab\u043e\u0442\u043c\u0435\u043d\u0438\u0442\u044c\u00bb \u043d\u0435\u0442. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u0430\u0436\u0435 \u043f\u043e\u0441\u043b\u0435 \u043b\u043e\u0433\u0430\u0443\u0442\u0430 \u0443\u043a\u0440\u0430\u0434\u0435\u043d\u043d\u044b\u0439 access\u2011token \u0435\u0449\u0451 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0443\u0442 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c. \u0412\u00a0\u0431\u043e\u0435\u0432\u044b\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 \u0441\u00a0\u044d\u0442\u0438\u043c \u0431\u043e\u0440\u044e\u0442\u0441\u044f: \u0445\u0440\u0430\u043d\u044f\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u043e\u0442\u043e\u0437\u0432\u0430\u043d\u043d\u044b\u0445 \u0442\u043e\u043a\u0435\u043d\u043e\u0432 (\u00ab\u0447\u0451\u0440\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a\u00bb)\u00a0\u043b\u0438\u0431\u043e \u0434\u0435\u0440\u0436\u0430\u0442 refresh \u0432\u00a0\u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0430\u044e\u0442 \u0435\u0433\u043e \u043f\u0440\u0438\u00a0\u043b\u043e\u0433\u0430\u0443\u0442\u0435.<\/p>\n<p><strong>Refresh \u0441\u0442\u043e\u0438\u0442 \u0434\u0435\u043b\u0430\u0442\u044c \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u043c.<\/strong> \u041c\u044b \u043f\u0440\u0438\u00a0\u043a\u0430\u0436\u0434\u043e\u043c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u0432\u044b\u0434\u0430\u0451\u043c \u043d\u043e\u0432\u0443\u044e \u043f\u0430\u0440\u0443 \u0442\u043e\u043a\u0435\u043d\u043e\u0432. \u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043f\u0440\u0438\u00a0\u044d\u0442\u043e\u043c \u0435\u0449\u0451 \u0438 \u0438\u043d\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0442\u0430\u0440\u044b\u0439 refresh: \u0435\u0441\u043b\u0438 \u043a\u0442\u043e\u2011\u0442\u043e \u043f\u043e\u043f\u044b\u0442\u0430\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u0432\u0442\u043e\u0440\u043e\u0439 \u0440\u0430\u0437\u00a0\u2014 \u043f\u043e\u0447\u0442\u0438 \u043d\u0430\u0432\u0435\u0440\u043d\u044f\u043a\u0430 \u0442\u043e\u043a\u0435\u043d \u0443\u043a\u0440\u0430\u043b\u0438, \u0438 \u043d\u0443\u0436\u043d\u043e \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u041a\u0430\u043a\u00a0\u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c: \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0441\u044f \u043e\u0434\u0438\u043d \u0440\u0430\u0437, \u0430\u00a0\u043f\u0440\u0438\u00a0\u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0439 \u043f\u043e\u043f\u044b\u0442\u043a\u0435\u00a0\u2014 \u0442\u0440\u0435\u0432\u043e\u0433\u0430.<\/p>\n<p><strong>\u041a\u043b\u044e\u0447\u0438 \u043d\u0435\u00a0\u0432\u00a0\u043a\u043e\u0434\u0435.<\/strong> \u0412\u00a0\u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043a\u043b\u044e\u0447\u0438 \u0437\u0430\u0448\u0438\u0442\u044b \u043f\u0440\u044f\u043c\u043e \u0432\u00a0\u043a\u043e\u0434\u00a0\u2014 \u0434\u043b\u044f\u00a0\u0441\u0442\u0430\u0442\u044c\u0438 \u044d\u0442\u043e \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e, \u043d\u043e\u00a0\u0432\u00a0\u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0438\u0445 \u0432\u044b\u043d\u043e\u0441\u044f\u0442 \u0432\u00a0\u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f (env)\u00a0\u2014 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435\u00a0\u043b\u0435\u0436\u0430\u0442 \u0432\u00a0\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438. \u0418\u043d\u0430\u0447\u0435 \u043b\u044e\u0431\u043e\u0439, \u043a\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0434\u043e\u0441\u0442\u0443\u043f \u043a\u00a0\u043a\u043e\u0434\u0443, \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0438 \u043a\u043b\u044e\u0447\u0438 \u043e\u0442\u00a0\u0432\u0441\u0435\u0445 \u0442\u043e\u043a\u0435\u043d\u043e\u0432.<\/p>\n<p><strong>SameSite=Strict \u0441\u0442\u0440\u043e\u0433\u0438\u0439, \u0438\u043d\u043e\u0433\u0434\u0430 \u0441\u043b\u0438\u0448\u043a\u043e\u043c.<\/strong> \u0421\u00a0\u043d\u0438\u043c cookie \u043d\u0435\u00a0\u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u0440\u0438\u0448\u0451\u043b \u043f\u043e\u00a0\u0432\u043d\u0435\u0448\u043d\u0435\u0439 \u0441\u0441\u044b\u043b\u043a\u0435\u00a0\u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043a\u043b\u0438\u043a\u043d\u0443\u043b \u043f\u043e\u00a0\u0441\u0441\u044b\u043b\u043a\u0435 \u0432\u00a0\u043f\u0438\u0441\u044c\u043c\u0435 \u043d\u0430\u00a0\u0432\u0430\u0448 \u0441\u0430\u0439\u0442. \u0414\u043b\u044f\u00a0\u0431\u0435\u043a\u0435\u043d\u0434\u0430 \u043e\u043d \u043e\u043a\u0430\u0436\u0435\u0442\u0441\u044f \u00ab\u043d\u0435 \u0437\u0430\u043b\u043e\u0433\u0438\u043d\u0435\u043d\u043d\u044b\u043c\u00bb, \u043f\u043e\u043a\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435\u00a0\u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0440\u043e\u0441\u0438\u0442 \u0442\u043e\u043a\u0435\u043d\u044b. \u0411\u043e\u043b\u0435\u0435 \u043c\u044f\u0433\u043a\u0438\u0439 \u0440\u0435\u0436\u0438\u043c Lax \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442 cookie \u043f\u0440\u0438\u00a0\u0442\u0430\u043a\u0438\u0445 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430\u0445, \u043d\u043e\u00a0\u0442\u043e\u0433\u0434\u0430 \u0441\u0442\u043e\u0438\u0442 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0437\u0430\u0449\u0438\u0442\u0443 \u043e\u0442\u00a0CSRF\u00a0\u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442, \u0447\u0442\u043e\u00a0\u0437\u0430\u043f\u0440\u043e\u0441 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u0438\u0448\u0451\u043b \u0441\u00a0\u0432\u0430\u0448\u0435\u0433\u043e \u0441\u0430\u0439\u0442\u0430.<\/p>\n<p><strong>\u0422\u043e\u043a\u0435\u043d\u00a0\u2014 \u044d\u0442\u043e \u043e\u0442\u043a\u0440\u044b\u0442\u043a\u0430.<\/strong> \u041e\u0431\u00a0\u044d\u0442\u043e\u043c \u0443\u0436\u0435\u00a0\u0431\u044b\u043b\u043e \u0432\u044b\u0448\u0435, \u043d\u043e\u00a0\u043f\u043e\u0432\u0442\u043e\u0440\u044e: \u043d\u0435\u00a0\u043a\u043b\u0430\u0434\u0438\u0442\u0435 \u0432\u00a0\u0442\u043e\u043a\u0435\u043d \u043f\u0430\u0440\u043e\u043b\u0438 \u0438 \u043b\u044e\u0431\u044b\u0435 \u0441\u0435\u043a\u0440\u0435\u0442\u044b. \u0412\u0441\u0451, \u0447\u0442\u043e\u00a0\u043b\u0435\u0436\u0438\u0442 \u0432\u043d\u0443\u0442\u0440\u0438 \u0442\u043e\u043a\u0435\u043d\u0430, \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u043a\u0442\u043e \u0443\u0433\u043e\u0434\u043d\u043e.<\/p>\n<h3>\u0418\u0442\u043e\u0433\u0438<\/h3>\n<p>\u041c\u044b \u0441\u043e\u0431\u0440\u0430\u043b\u0438 \u043f\u043e\u043b\u043d\u0443\u044e \u0441\u0445\u0435\u043c\u0443 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438: \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438, \u043a\u0430\u043a\u00a0\u0443\u0441\u0442\u0440\u043e\u0435\u043d JWT \u0432\u043d\u0443\u0442\u0440\u0438 (Base64Url + HMAC SHA\u2011256), \u0437\u0430\u0447\u0435\u043c access \u0438 refresh \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u043a\u043b\u044e\u0447\u0430\u043c\u0438 \u0438 \u0438\u043c\u0435\u044e\u0442 \u0440\u0430\u0437\u043d\u044b\u0435 \u0442\u0438\u043f\u044b, \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044e, \u043b\u043e\u0433\u0438\u043d, \u043b\u043e\u0433\u0430\u0443\u0442 \u0438 \u0440\u043e\u0442\u0430\u0446\u0438\u044e \u0442\u043e\u043a\u0435\u043d\u043e\u0432 \u043d\u0430\u00a0Go. \u041d\u0430\u00a0\u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0435\u00a0\u2014 access\u2011token \u0432\u00a0\u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 (store), \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0447\u0435\u0440\u0435\u0437 \u0444\u043b\u0430\u0433 + <code>\/refresh<\/code>, \u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u044b\u0435 \u0440\u043e\u0443\u0442\u044b \u0438 \u043d\u0435\u0437\u0430\u043c\u0435\u0442\u043d\u043e\u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043f\u0440\u0438 401\u00a0\u0447\u0435\u0440\u0435\u0437 axios\u2011interceptor.<\/p>\n<p>\u041a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044f \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u0442\u0441\u044f \u043d\u0430\u00a0\u043b\u044e\u0431\u043e\u0439 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u2011\u0441\u0442\u0435\u043a: \u0432\u043e\u00a0Vue 3\u00a0\u044d\u0442\u043e <code>router.beforeEach<\/code> \u0438 Vuex, \u0432\u00a0React\/Next.js\u00a0\u2014 \u0441\u0432\u043e\u0438 guard\u2011\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u044b, \u043d\u043e\u00a0\u043b\u043e\u0433\u0438\u043a\u0430 \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u0442\u043e\u0439\u00a0\u0436\u0435. \u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0441\u0442\u0430\u0442\u044c\u044f \u0441\u044d\u043a\u043e\u043d\u043e\u043c\u0438\u043b\u0430 \u0432\u0430\u043c \u0432\u0440\u0435\u043c\u044f \u0438 \u0443\u0431\u0435\u0440\u0435\u0433\u043b\u0430 \u043e\u0442\u00a0\u043f\u0430\u0440\u044b \u0433\u0440\u0430\u0431\u043b\u0435\u0439. \u0421\u043f\u0430\u0441\u0438\u0431\u043e, \u0447\u0442\u043e\u00a0\u0434\u043e\u0447\u0438\u0442\u0430\u043b\u0438!<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1072430\/\">https:\/\/habr.com\/ru\/articles\/1072430\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0422\u0435\u043c\u0430 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u043e\u0432\u00a0\u2014 \u043e\u0434\u043d\u0430 \u0438\u0437\u00a0\u0441\u0430\u043c\u044b\u0445 \u0431\u043e\u043b\u044c\u043d\u044b\u0445 \u0432\u00a0\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438. \u041c\u043d\u043e\u0433\u0438\u0435, \u043d\u0435\u00a0\u0434\u043e\u00a0\u043a\u043e\u043d\u0446\u0430 \u043f\u043e\u043d\u0438\u043c\u0430\u044f, \u043a\u0430\u043a\u00a0\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0434\u043e\u043b\u0436\u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c, \u043a\u043b\u0430\u0434\u0443\u0442 \u043e\u0431\u0430 \u0442\u043e\u043a\u0435\u043d\u0430 \u0432\u00a0cookie \u0438\u043b\u0438\u00a0\u2014 \u0447\u0442\u043e\u00a0\u0435\u0449\u0451 \u0445\u0443\u0436\u0435\u00a0\u2014 \u0432\u00a0localStorage. \u0412\u00a0\u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u0441\u043e\u0431\u0435\u0440\u0451\u043c \u0441\u0445\u0435\u043c\u0443, \u0432\u00a0\u043a\u043e\u0442\u043e\u0440\u043e\u0439 access\u2011token \u0436\u0438\u0432\u0451\u0442 \u0432\u00a0\u043f\u0430\u043c\u044f\u0442\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, refresh\u2011token\u00a0\u2014 \u0432\u00a0HttpOnly\u2011cookie, \u0430\u00a0\u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u0441\u0430\u043c \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u043d\u0435\u0437\u0430\u043c\u0435\u0442\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u043f\u0440\u043e\u0442\u0443\u0445\u0448\u0438\u0439 access.\u0411\u0435\u043a\u0435\u043d\u0434\u00a0\u2014 \u043d\u0430\u00a0Go, \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u00a0\u2014 \u043d\u0430\u00a0Vue 3\u00a0(Vuex + vue\u2011router + axios), \u043d\u043e\u00a0\u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044f \u0442\u0430\u043a\u00a0\u0436\u0435 \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u043c\u0430 \u043a\u00a0React \u0438 Next.js. \u0412\u0435\u0440\u0441\u0442\u043a\u0443 \u0438 \u0441\u0442\u0438\u043b\u0438 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e \u0437\u0430\u00a0\u043a\u0430\u0434\u0440\u043e\u043c\u00a0\u2014 \u0432\u00a0\u0441\u0442\u0430\u0442\u044c\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043b\u043e\u0433\u0438\u043a\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.\u0417\u0430\u0447\u0435\u043c \u043d\u0443\u0436\u043d\u044b \u0434\u0432\u0430 \u0442\u043e\u043a\u0435\u043d\u0430\u041f\u0440\u043e\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u043f\u043e\u043d\u044f\u0442\u044c \u043d\u0430\u00a0\u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0438 \u0441\u00a0\u0444\u0435\u0441\u0442\u0438\u0432\u0430\u043b\u0435\u043c:access_token\u00a0\u2014 \u044d\u0442\u043e \u0431\u0440\u0430\u0441\u043b\u0435\u0442. \u0415\u0433\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e\u0442 \u043d\u0430\u00a0\u043a\u0430\u0436\u0434\u043e\u043c \u0432\u0445\u043e\u0434\u0435, \u0442\u043e \u0435\u0441\u0442\u044c \u043e\u043d \u043f\u0440\u0438\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u00a0\u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043a\u00a0API. \u0416\u0438\u0432\u0451\u0442 \u043e\u043d \u043d\u0435\u0434\u043e\u043b\u0433\u043e: \u043e\u0431\u044b\u0447\u043d\u043e 5\u201315\u00a0\u043c\u0438\u043d\u0443\u0442.refresh_token\u00a0\u2014 \u044d\u0442\u043e \u043f\u0430\u0441\u043f\u043e\u0440\u0442. \u0415\u0433\u043e \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u00a0\u043a\u0430\u0441\u0441\u0435\u00a0\u2014 \u043d\u0430\u00a0\u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0435 \/refresh\u00a0\u2014 \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u0431\u0440\u0430\u0441\u043b\u0435\u0442. \u0416\u0438\u0432\u0451\u0442 \u043e\u043d \u0434\u043d\u0438 \u0438 \u0434\u0430\u0436\u0435 \u043c\u0435\u0441\u044f\u0446\u044b.\u041b\u043e\u0433\u0438\u043a\u0430 \u0442\u0430\u043a\u0430\u044f: \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0431\u0440\u0430\u0441\u043b\u0435\u0442 \u0441\u0440\u0438\u0441\u0443\u044e\u0442 (access \u0443\u0442\u0435\u0447\u0451\u0442), \u043e\u043d \u0441\u043a\u043e\u0440\u043e \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043d\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c. \u0410 \u00ab\u043f\u0430\u0441\u043f\u043e\u0440\u0442\u00bb \u043c\u044b \u043f\u0440\u044f\u0447\u0435\u043c \u0432\u00a0HttpOnly\u2011cookie, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043d\u0435\u00a0\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0438\u0437\u00a0JavaScript.\u0427\u0442\u043e\u00a0\u0443 \u0442\u043e\u043a\u0435\u043d\u0430 \u0432\u043d\u0443\u0442\u0440\u0438: \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430 \u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u044cJWT\u2011\u0442\u043e\u043a\u0435\u043d\u00a0\u2014 \u044d\u0442\u043e \u0441\u0442\u0440\u043e\u043a\u0430 \u0438\u0437\u00a0\u0442\u0440\u0451\u0445 \u0447\u0430\u0441\u0442\u0435\u0439, \u0440\u0430\u0437\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0445 \u0442\u043e\u0447\u043a\u0430\u043c\u0438: header.payload.signature.header\u00a0\u2014 JSON \u0441\u00a0\u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u043e\u043c \u043f\u043e\u0434\u043f\u0438\u0441\u0438;payload\u00a0\u2014 JSON \u0441\u00a0\u0434\u0430\u043d\u043d\u044b\u043c\u0438 (claims): \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0442\u0435\u043b\u0435\u0444\u043e\u043d \u0438 \u0441\u0440\u043e\u043a \u0436\u0438\u0437\u043d\u0438;signature\u00a0\u2014 \u201c\u043f\u0435\u0447\u0430\u0442\u044c\u201d.\u041f\u0435\u0440\u0432\u044b\u0435 \u0434\u0432\u0435 \u0447\u0430\u0441\u0442\u0438 \u0437\u0430\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u0432 Base64Url\u00a0\u2014 \u044d\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u044b\u0439 Base64, \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u00a0\u0441\u0438\u043c\u0432\u043e\u043b\u0430\u043c\u0438 &#8212; \u0438 _ \u0432\u043c\u0435\u0441\u0442\u043e + \u0438 \/ \u0438 \u0431\u0435\u0437 = \u043d\u0430\u00a0\u043a\u043e\u043d\u0446\u0435, \u0447\u0442\u043e\u0431\u044b \u0442\u043e\u043a\u0435\u043d \u043c\u043e\u0436\u043d\u043e\u00a0\u0431\u044b\u043b\u043e \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u00a0URL \u0438 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u0445. \u0412\u0430\u0436\u043d\u043e: Base64Url\u00a0\u2014 \u044d\u0442\u043e \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430, \u0430\u00a0\u043d\u0435\u00a0\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u0435. Payload \u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u043a\u0442\u043e \u0443\u0433\u043e\u0434\u043d\u043e. \u042d\u0442\u043e \u043a\u0430\u043a\u00a0\u043e\u0442\u043a\u0440\u044b\u0442\u043a\u0430: \u0434\u043e\u00a0\u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0430 \u0434\u043e\u0439\u0434\u0451\u0442, \u043d\u043e\u00a0\u0442\u0435\u043a\u0441\u0442 \u043f\u043e\u00a0\u0434\u043e\u0440\u043e\u0433\u0435 \u0447\u0438\u0442\u0430\u0435\u0442 \u043a\u0442\u043e \u0445\u043e\u0447\u0435\u0442. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u00a0\u0442\u043e\u043a\u0435\u043d \u043d\u0435\u043b\u044c\u0437\u044f \u043a\u043b\u0430\u0441\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u0438 \u0438 \u043b\u044e\u0431\u044b\u0435 \u0441\u0435\u043a\u0440\u0435\u0442\u044b.\u0422\u0440\u0435\u0442\u044c\u044f \u0447\u0430\u0441\u0442\u044c\u00a0\u2014 \u043f\u043e\u0434\u043f\u0438\u0441\u044c. \u0412\u00a0\u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044d\u0442\u043e HMAC SHA\u2011256: \u0431\u0435\u0440\u0451\u043c \u0441\u0442\u0440\u043e\u043a\u0443 header.payload, \u043f\u0440\u043e\u0433\u043e\u043d\u044f\u0435\u043c \u0447\u0435\u0440\u0435\u0437 HMAC \u0441\u00a0\u0441\u0435\u043a\u0440\u0435\u0442\u043d\u044b\u043c \u043a\u043b\u044e\u0447\u043e\u043c \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0441\u0442\u0440\u043e\u043a\u0443, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u0434\u0435\u043b\u0430\u0442\u044c, \u043d\u0435\u00a0\u0437\u043d\u0430\u044f \u043a\u043b\u044e\u0447\u0430. \u0415\u0441\u043b\u0438 \u0445\u043e\u0442\u044c \u043e\u0434\u0438\u043d \u0441\u0438\u043c\u0432\u043e\u043b payload \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c\u00a0\u2014 \u00ab\u043f\u0435\u0447\u0430\u0442\u044c\u00bb \u043d\u0435\u00a0\u0441\u043e\u0439\u0434\u0451\u0442\u0441\u044f.\u0421\u043e\u0431\u0435\u0440\u0451\u043c \u0442\u043e\u043a\u0435\u043d \u0440\u0443\u043a\u0430\u043c\u0438, \u0447\u0442\u043e\u0431\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0439 \u0448\u0430\u0433:\/\/ \u0438\u043c\u043f\u043e\u0440\u0442\u044b: crypto\/hmac, crypto\/sha256, encoding\/base64, encoding\/json, timefunc b64Encode(data []byte) string { return base64.RawURLEncoding.EncodeToString(data) }\/\/ \u0420\u0443\u0447\u043d\u0430\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430 (HS256 = HMAC + SHA-256)func manualSign(payload map[string]any, key []byte) (string, error) {    header, _ := json.Marshal(map[string]string{&#171;alg&#187;: &#171;HS256&#187;, &#171;typ&#187;: &#171;JWT&#187;})    body, err := json.Marshal(payload)    if err != nil {        return &#171;&#187;, err    }    signingInput := b64Encode(header) + &#171;.&#187; + b64Encode(body) \/\/ \u044d\u0442\u043e \u043c\u044b \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c    mac := hmac.New(sha256.New, key) \/\/ \u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 HMAC SHA-256    mac.Write([]byte(signingInput))    return signingInput + &#171;.&#187; + b64Encode(mac.Sum(nil)), nil}token, _ := manualSign(map[string]any{    &#171;phone&#187;: &#171;+79999999998&#187;,    &#171;type&#187;:  &#171;access&#187;,    &#171;exp&#187;:   time.Now().Add(15 * time.Minute).Unix(),}, jwtKey)\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430\u00a0\u2014 \u0432\u00a0\u043e\u0431\u0440\u0430\u0442\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435:func manualVerify(token string, key []byte) (map[string]any, error) {    parts := strings.Split(token, &#171;.&#187;)    if len(parts) != 3 {        return nil, errors.New(&#171;malformed token&#187;)    }    \/\/ 1. \u041f\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u043c \u00ab\u043f\u0435\u0447\u0430\u0442\u044c\u00bb \u0438 \u0441\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u0435\u043c \u0432 constant-time    mac := hmac.New(sha256.New, key)    mac.Write([]byte(parts[0] + &#171;.&#187; + parts[1]))    if !hmac.Equal([]byte(b64Encode(mac.Sum(nil))), []byte(parts[2])) {        return nil, errors.New(&#171;invalid signature&#187;)    }    \/\/ 2. \u041f\u0435\u0447\u0430\u0442\u044c \u0441\u043e\u0448\u043b\u0430\u0441\u044c \u2014 \u0434\u0435\u043a\u043e\u0434\u0438\u0440\u0443\u0435\u043c payload    body, _ := base64.RawURLEncoding.DecodeString(parts[1])    var payload map[string]any    if err := json.Unmarshal(body, &amp;payload); err != nil {        return nil, err    }    \/\/ 3. \u041f\u043e\u0434\u043f\u0438\u0441\u044c \u0432\u0430\u043b\u0438\u0434\u043d\u0430, \u043d\u043e \u0442\u043e\u043a\u0435\u043d \u043c\u043e\u0433 \u043f\u0440\u043e\u0442\u0443\u0445\u043d\u0443\u0442\u044c \u2014 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0441\u0440\u043e\u043a    if exp, ok := payload[&#171;exp&#187;].(float64); ok &amp;&amp; time.Now().Unix() &gt; int64(exp) {        return nil, errors.New(&#171;token expired&#187;)    }    return payload, nil}\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430\u00a0\u0448\u0430\u0433 3: \u0432\u0430\u043b\u0438\u0434\u043d\u0430\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u044c \u043d\u0435\u00a0\u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d\u00a0\u2014 \u0441\u0440\u043e\u043a \u0436\u0438\u0437\u043d\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e. \u0412\u00a0\u0440\u0443\u0447\u043d\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0442\u0430\u043a\u0438\u0435 \u0432\u0435\u0449\u0438 \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u0431\u044b\u0442\u044c, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u00a0\u0431\u043e\u044e \u043f\u0440\u043e\u0449\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443\u00a0\u2014 \u043d\u0438\u0436\u0435 \u043c\u044b \u0442\u0430\u043a \u0438 \u0441\u0434\u0435\u043b\u0430\u0435\u043c.\u0414\u0432\u0435 \u0430\u0442\u0430\u043a\u0438, \u043e\u00a0\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0441\u0442\u043e\u0438\u0442 \u0437\u043d\u0430\u0442\u044calg: none. \u0410\u0442\u0430\u043a\u0443\u044e\u0449\u0438\u0439 \u043f\u0438\u0448\u0435\u0442 \u0432\u00a0header \u201c\u0442\u043e\u043a\u0435\u043d \u043d\u0435\u00a0\u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u201d \u0438 \u043d\u0430\u0434\u0435\u0435\u0442\u0441\u044f, \u0447\u0442\u043e\u00a0\u0441\u0435\u0440\u0432\u0435\u0440 \u043f\u043e\u0432\u0435\u0440\u0438\u0442 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0443. \u0417\u0430\u0449\u0438\u0442\u0430: \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435\u00a0\u0432\u0435\u0440\u0438\u0442 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0443 \u0438 \u0434\u0435\u0440\u0436\u0438\u0442 \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043d\u043d\u044b\u0445 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u043e\u0432.\u041f\u043e\u0434\u043c\u0435\u043d\u0430 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0430. \u0421\u0435\u0440\u0432\u0435\u0440 \u0436\u0434\u0451\u0442 \u043e\u0434\u0438\u043d \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c, \u0430\u00a0\u0430\u0442\u0430\u043a\u0443\u044e\u0449\u0438\u0439 \u0448\u043b\u0451\u0442 \u0442\u043e\u043a\u0435\u043d, \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 \u0434\u0440\u0443\u0433\u0438\u043c, \u0432\u00a0\u0440\u0430\u0441\u0447\u0451\u0442\u0435 \u043d\u0430\u00a0\u043f\u0443\u0442\u0430\u043d\u0438\u0446\u0443. \u0417\u0430\u0449\u0438\u0442\u0430 \u0442\u0430\u00a0\u0436\u0435: \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a.\u0412 golang-jwt \u044d\u0442\u043e \u043e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430: jwt.WithValidMethods([]string{&#171;HS256&#187;}).\u041f\u043e\u0447\u0435\u043c\u0443 access \u0438 refresh \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u043a\u043b\u044e\u0447\u0430\u043c\u0438\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435, \u0447\u0442\u043e\u00a0\u043c\u044b \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043b\u0438 \u043e\u0431\u0430 \u0442\u043e\u043a\u0435\u043d\u0430 \u043e\u0434\u043d\u0438\u043c \u043a\u043b\u044e\u0447\u043e\u043c \u0441\u00a0\u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438. \u0422\u043e\u0433\u0434\u0430 \u0434\u043e\u043b\u0433\u043e\u0436\u0438\u0432\u0443\u0449\u0438\u0439 refresh \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u044c \u0432\u0441\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043a\u0430\u043a\u00a0access\u00a0\u2014 \u0430\u0442\u0430\u043a\u0443\u044e\u0449\u0438\u0439 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u0435\u0447\u043d\u044b\u0439 \u00ab\u0431\u0440\u0430\u0441\u043b\u0435\u0442\u00bb. \u041f\u043e\u044d\u0442\u043e\u043c\u0443:access \u0438 refresh \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u043a\u043b\u044e\u0447\u0430\u043c\u0438;\u0434\u043b\u044f\u00a0\u043d\u0430\u0434\u0451\u0436\u043d\u043e\u0441\u0442\u0438 \u0432\u00a0\u0441\u0430\u043c \u0442\u043e\u043a\u0435\u043d \u043a\u043b\u0430\u0434\u0451\u043c \u043f\u043e\u043b\u0435 type (&#171;access&#187; \u0438\u043b\u0438 &#171;refresh&#187;) \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0435\u0433\u043e \u043d\u0430\u00a0\u0441\u0435\u0440\u0432\u0435\u0440\u0435.\u0412\u00a0\u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f\u0445 \u0442\u043e\u043a\u0435\u043d\u0430\u043c \u0435\u0449\u0451 \u0434\u0430\u044e\u0442 \u0440\u0430\u0437\u043d\u044b\u0435 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u044b \u0432\u00a0\u0441\u0442\u0440\u043e\u043a\u0435. \u0421\u043c\u044b\u0441\u043b \u043e\u0434\u0438\u043d: access \u0438 refresh\u00a0\u2014 \u0440\u0430\u0437\u043d\u044b\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u0438 \u043d\u0435\u00a0\u0434\u043e\u043b\u0436\u043d\u044b\u00a0\u0431\u044b\u0442\u044c \u0432\u0437\u0430\u0438\u043c\u043e\u0437\u0430\u043c\u0435\u043d\u044f\u0435\u043c\u044b.\u0411\u0435\u043a\u0435\u043d\u0434 \u043d\u0430\u00a0Go\u041d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0435, \u043d\u043e\u00a0\u0432\u0430\u0436\u043d\u043e\u0435 \u0437\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u0435: \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u044b\u0439 \u043a\u043e\u0433\u0434\u0430\u2011\u0442\u043e github.com\/dgrijalva\/jwt-go \u0437\u0430\u0431\u0440\u043e\u0448\u0435\u043d \u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0435 \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u0438. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0439 \u0444\u043e\u0440\u043a github.com\/golang-jwt\/jwt\/v5.\u041e\u0431\u0432\u044f\u0437\u043a\u0430:package mainimport (    &#171;database\/sql&#187;    &#171;encoding\/json&#187;    &#171;log&#187;    &#171;net\/http&#187;    &#171;os&#187;    &#171;strings&#187;    &#171;time&#187;    &#171;github.com\/golang-jwt\/jwt\/v5&#187;    &#171;github.com\/gorilla\/mux&#187;    _ &#171;github.com\/lib\/pq&#187;    &#171;github.com\/rs\/cors&#187;    &#171;golang.org\/x\/crypto\/bcrypt&#187;)var (    db              *sql.DB    jwtKey          = []byte(os.Getenv(&#171;JWT_SECRET&#187;))         \/\/ \u0432 \u043f\u0440\u043e\u0434\u0435 \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0437 env    refreshTokenKey = []byte(os.Getenv(&#171;JWT_REFRESH_SECRET&#187;)) \/\/ \u0440\u0430\u0437\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438 \u0434\u043b\u044f access \u0438 refresh    secureCookies   = os.Getenv(&#171;SECURE_COOKIES&#187;) == &#171;true&#187;   \/\/ \u0434\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e HTTP \u2014 false)type User struct {    ID       int    `json:&#187;id&#187;`    Username string `json:&#187;username&#187;` \/\/ \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0435    Phone    string `json:&#187;phone&#187;`    \/\/ \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0435    Name     string `json:&#187;name&#187;`    Lastname string `json:&#187;lastname&#187;`    Password string `json:&#187;password&#187;`    Icons    string `json:&#187;icons&#187;`}\/\/ \u041e\u0448\u0438\u0431\u043a\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 JSONfunc writeError(w http.ResponseWriter, message string, status int) {    w.WriteHeader(status)    json.NewEncoder(w).Encode(map[string]string{&#171;result&#187;: message})}\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u043e\u0432\u00a0\u2014 \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u0441\u0440\u043e\u043a\u043e\u043c \u0436\u0438\u0437\u043d\u0438, \u043a\u043b\u044e\u0447\u043e\u043c \u0438 \u0442\u0438\u043f\u043e\u043c:type Claims struct {    Phone string `json:&#187;phone&#187;`    Type  string `json:&#187;type&#187;` \/\/ &#171;access&#187; \u0438\u043b\u0438 &#171;refresh&#187;    jwt.RegisteredClaims}func generateAccessToken(phone string) (string, error) {    claims := &amp;Claims{        Phone: phone,        Type:  &#171;access&#187;,        RegisteredClaims: jwt.RegisteredClaims{            \/\/ 15 \u043c\u0438\u043d\u0443\u0442 \u0432 \u043f\u0440\u043e\u0434\u0435; \u0434\u043b\u044f \u0434\u0435\u043c\u043e \u043f\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 1 \u043c\u0438\u043d\u0443\u0442\u0443,            \/\/ \u0447\u0442\u043e\u0431\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0443 \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u0447\u0438\u043a\u0430 \u043d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0435            ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),        },    }    return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(jwtKey)}func generateRefreshToken(phone string) (string, error) {    claims := &amp;Claims{        Phone: phone,        Type:  &#171;refresh&#187;,        RegisteredClaims: jwt.RegisteredClaims{            ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),        },    }    return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(refreshTokenKey)}\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f\u0413\u043b\u0443\u0431\u043e\u043a\u043e \u0437\u0430\u043a\u0430\u043f\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0435\u00a0\u0431\u0443\u0434\u0435\u043c: \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c username \u0438 phone, \u0445\u044d\u0448\u0438\u0440\u0443\u0435\u043c \u043f\u0430\u0440\u043e\u043b\u044c bcrypt \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c:func registerHandler(w http.ResponseWriter, r *http.Request) {    var user User    if err := json.NewDecoder(r.Body).Decode(&amp;user); err != nil {        writeError(w, err.Error(), http.StatusBadRequest)        return    }    var existing string    if db.QueryRow(&#171;SELECT username FROM users WHERE username=$1&#187;, user.Username).Scan(&amp;existing) == nil {        writeError(w, &#171;\u041d\u0438\u043a\u043d\u0435\u0439\u043c \u0437\u0430\u043d\u044f\u0442&#187;, http.StatusConflict)        return    }    if db.QueryRow(&#171;SELECT phone FROM users WHERE phone=$1&#187;, user.Phone).Scan(&amp;existing) == nil {        writeError(w, &#171;\u0422\u0435\u043b\u0435\u0444\u043e\u043d \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d&#187;, http.StatusConflict)        return    }    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)    if err != nil {        writeError(w, err.Error(), http.StatusInternalServerError)        return    }    _, err = db.Exec(        &#171;INSERT INTO users (username, phone, name, lastname, password, icons) VALUES ($1,$2,$3,$4,$5,$6)&#187;,        user.Username, user.Phone, user.Name, user.Lastname, hashedPassword, user.Icons,    )    if err != nil {        writeError(w, err.Error(), http.StatusInternalServerError)        return    }    w.Header().Set(&#171;Content-Type&#187;, &#171;application\/json&#187;)    json.NewEncoder(w).Encode(map[string]string{&#171;message&#187;: &#171;\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0448\u043b\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e!&#187;})}\u041b\u043e\u0433\u0438\u043d\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0442\u0435\u043b\u0435\u0444\u043e\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c, \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u043f\u0430\u0440\u0443 \u0442\u043e\u043a\u0435\u043d\u043e\u0432. Refresh \u0441\u0442\u0430\u0432\u0438\u043c \u0432\u00a0\u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u0443\u044e cookie, access \u043e\u0442\u0434\u0430\u0451\u043c \u0432\u00a0\u0442\u0435\u043b\u0435 \u043e\u0442\u0432\u0435\u0442\u0430:func loginHandler(w http.ResponseWriter, r *http.Request) {    var user User    if err := json.NewDecoder(r.Body).Decode(&amp;user); err != nil {        writeError(w, err.Error(), http.StatusBadRequest)        return    }    var storedPassword string    err := db.QueryRow(&#171;SELECT password FROM users WHERE phone=$1&#187;, user.Phone).Scan(&amp;storedPassword)    if err != nil || bcrypt.CompareHashAndPassword([]byte(storedPassword), []byte(user.Password)) != nil {        writeError(w, &#171;\u041b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u043d\u0435 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u044e\u0442&#187;, http.StatusUnauthorized)        return    }    accessToken, err := generateAccessToken(user.Phone)    if err != nil {        writeError(w, err.Error(), http.StatusInternalServerError)        return    }    refreshToken, err := generateRefreshToken(user.Phone)    if err != nil {        writeError(w, err.Error(), http.StatusInternalServerError)        return    }    \/\/ Refresh \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u0443\u044e cookie    http.SetCookie(w, &amp;http.Cookie{        Name:     &#171;refresh_token&#187;,        Value:    refreshToken,        Path:     &#171;\/&#187;,        HttpOnly: true,           \/\/ JS \u043d\u0435 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0435\u0442 cookie \u2014 \u0437\u0430\u0449\u0438\u0442\u0430&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-491561","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/491561","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=491561"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/491561\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=491561"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=491561"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=491561"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}