{"id":492,"date":"2026-06-14T11:39:22","date_gmt":"2026-06-14T11:39:22","guid":{"rendered":"https:\/\/hyc.eshachem.com\/program\/?p=492"},"modified":"2026-06-18T04:48:39","modified_gmt":"2026-06-18T04:48:39","slug":"google-sso-x-next-js-%e5%af%a6%e4%bd%9c%e6%89%8b%e5%86%8a","status":"publish","type":"post","link":"https:\/\/hyc.eshachem.com\/program\/google-sso-x-next-js-%e5%af%a6%e4%bd%9c%e6%89%8b%e5%86%8a\/","title":{"rendered":"Google SSO \u00d7 Next.js \u5be6\u4f5c\u624b\u518a"},"content":{"rendered":"\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>\u9069\u7528\u74b0\u5883<\/strong>\uff1aNext.js 14+ App Router\u3001Server action\u3001NextAuth.js v4\u3001TypeScript<br><strong>\u76ee\u6a19<\/strong>\uff1a\u7167\u672c\u6587\u4ef6\u5f9e\u96f6\u5b8c\u6210 Google \u55ae\u4e00\u767b\u5165\uff08SSO\uff09\uff0c\u542b\u767b\u5165\u9801\u3001Session \u8b80\u53d6\u3001\u672a\u767b\u5165\u5c0e\u56de\u3001\u767b\u51fa\u3002<br>\u6700\u5f8c\u66f4\u65b0\u65e5\u671f\uff1a06\/14\/2026 \uff5c \u6700\u5f8c\u66f4\u65b0\u4eba\u54e1\uff1a\u9673\u6cd3\u6bd3<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%e5%ae%8c%e6%88%90%e5%be%8c%e7%9a%84%e6%aa%94%e6%a1%88%e7%b5%90%e6%a7%8b\">\u5b8c\u6210\u5f8c\u7684\u6a94\u6848\u7d50\u69cb<\/h3>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">your-project\/\n\u251c\u2500\u2500 .env.local\n\u251c\u2500\u2500 app\/\n\u2502   \u251c\u2500\u2500 layout.tsx                          \u2190 \u4fee\u6539\uff1a\u5305 AuthProvider\n\u2502   \u251c\u2500\u2500 login\/page.tsx                      \u2190 \u65b0\u589e\n\u2502   \u2514\u2500\u2500 api\/auth\/[...nextauth]\/route.ts     \u2190 \u65b0\u589e\n\u251c\u2500\u2500 components\/\n\u2502   \u251c\u2500\u2500 login-form.tsx                      \u2190 \u65b0\u589e\n\u2502   \u251c\u2500\u2500 auth-button.tsx                     \u2190 \u65b0\u589e\uff08\u53ef\u9078\uff09\n\u2502   \u2514\u2500\u2500 providers\/session-provider.tsx      \u2190 \u65b0\u589e\n\u251c\u2500\u2500 lib\/\n\u2502   \u251c\u2500\u2500 auth-env.ts                         \u2190 \u65b0\u589e\n\u2502   \u251c\u2500\u2500 auth-options.ts                     \u2190 \u65b0\u589e\n\u2502   \u2514\u2500\u2500 auth.ts                             \u2190 \u65b0\u589e\n\u2514\u2500\u2500 types\/\n    \u2514\u2500\u2500 next-auth.d.ts                      \u2190 \u65b0\u589e<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e5%85%88%e4%ba%86%e8%a7%a3%e6%b5%81%e7%a8%8b\">\u5148\u4e86\u89e3\u6d41\u7a0b<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u5f9e\u4f7f\u7528\u8005\u9801\u9762\u5230\u5b8c\u6210google sso\u5927\u81f4\u53ef\u4ee5\u5206\u70ba\u4ee5\u4e0b\u5e7e\u500b\u6b65\u9a5f\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u6b65\u9a5f<\/th><th>\u767c\u751f\u4ec0\u9ebc\u4e8b<\/th><\/tr><\/thead><tbody><tr><td>\u2460<\/td><td>\u4f7f\u7528\u8005\u9020\u8a2a\u53d7\u4fdd\u8b77\u9801 \u2192 Server \u7528 <code>getSessionUser()<\/code> \u6aa2\u67e5 \u2192 \u672a\u767b\u5165\u5247 <code>redirect(\"\/login?callbackUrl=...\")<\/code><\/td><\/tr><tr><td>\u2461<\/td><td>\u767b\u5165\u9801\u986f\u793a\u300cGoogle \u767b\u5165\u300d\u2192 \u547c\u53eb <code>signIn(\"google\", { callbackUrl })<\/code><\/td><\/tr><tr><td>\u2462<\/td><td>\u700f\u89bd\u5668\u8df3\u81f3 Google \u6388\u6b0a\u9801<\/td><\/tr><tr><td>\u2463<\/td><td>\u4f7f\u7528\u8005\u540c\u610f \u2192 Google \u5c0e\u5411 <code>\/api\/auth\/callback\/google<\/code><\/td><\/tr><tr><td>\u2464<\/td><td>NextAuth \u63db\u53d6 token\uff0c\u5beb\u5165 Session Cookie<\/td><\/tr><tr><td>\u2465<\/td><td>\u81ea\u52d5\u8df3\u56de <code>callbackUrl<\/code>\uff08\u767b\u5165\u524d\u8981\u53bb\u7684\u9801\u9762\uff09<\/td><\/tr><tr><td>\u2466<\/td><td>\u53d7\u4fdd\u8b77\u9801\u518d\u6b21\u57f7\u884c <code>getSessionUser()<\/code> \u2192 \u6709\u503c \u2192 \u6b63\u5e38\u986f\u793a<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u95dc\u9375 URL\uff08Google Console \u5fc5\u9808\u8a3b\u518a redirect URI\uff09\uff1a<\/strong><br>\u8981\u5b8c\u6210Google S S O\uff0c\u4e00\u5207\u7684\u4e00\u5207\u6700\u95dc\u9375\u5c31\u662f\u8981\u544a\u8a34Google\u767b\u5165\u5b8c\u4e4b\u5f8c\u8981\u5c0e\u56de\u6211\u7684\u7db2\u7ad9\uff08\u4e0b\u7ae0\u6703\u8a73\u7d30\u8aaa\u660e\uff09\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">http:<span class=\"hljs-comment\">\/\/localhost:3000\/api\/auth\/callback\/google<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong><code>callbackUrl<\/code> \u662f\u4ec0\u9ebc\uff1f<\/strong><br>\u53d7\u4fdd\u8b77\u9801\u5728\u672a\u767b\u5165\u6642\uff0c\u628a\u300c\u767b\u5165\u5f8c\u8981\u56de\u5230\u54ea\u300d\u5beb\u5728 query string\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">\/login?callbackUrl=\/profile<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">\u767b\u5165\u6210\u529f\u5f8c NextAuth \u6703\u81ea\u52d5\u5c0e\u5411 <code>\/profile<\/code>\u3002<br><strong>\u5728\u958b\u59cb\u4e4b\u524d<\/strong><br>\u78ba\u4fdd<code>tsconfig.json<\/code> \u5df2\u8a2d\u5b9a <code>@\/*<\/code> \u8def\u5f91\u5225\u540d\uff08\u9810\u8a2d create-next-app \u5373\u6709\uff09<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%ad%a5%e9%a9%9f-1%ef%bc%9agoogle-cloud-console\">\u6b65\u9a5f 1\uff1aGoogle Cloud Console<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>\u76ee\u7684<\/strong>\uff1a\u5411 Google \u8a3b\u518a\u4f60\u7684\u7db2\u7ad9\uff0c\u53d6\u5f97 OAuth \u6191\u8b49\u3002<br><strong>\u672c\u6b65\u9a5f\u4e0d\u5beb\u7a0b\u5f0f\u3002<\/strong><\/p>\n<\/blockquote>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u958b\u555f <a href=\"https:\/\/console.cloud.google.com\/\">Google Cloud Console<\/a><\/li>\n\n\n\n<li>\u5efa\u7acb\u6216\u9078\u64c7\u5c08\u6848<\/li>\n\n\n\n<li><strong>APIs &amp; Services \u2192 OAuth consent screen<\/strong><\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>User Type\uff1a<strong>External<\/strong>\uff08\u6216 Workspace \u9078 Internal\uff09<\/li>\n\n\n\n<li>\u586b\u5beb\u61c9\u7528\u7a0b\u5f0f\u540d\u7a31\u3001\u652f\u63f4 Email<\/li>\n\n\n\n<li>\u82e5 External \u4e14<strong>\u672a\u767c\u5e03<\/strong>\uff1a\u5230 <strong>Test users<\/strong> \u52a0\u5165\u4f60\u7684 Google \u5e33\u865f<\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>APIs &amp; Services \u2192 Credentials \u2192 Create Credentials \u2192 OAuth client ID<\/strong><\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Application type\uff1a<strong>Web application<\/strong><\/li>\n\n\n\n<li><strong>Authorized JavaScript origins<\/strong>\uff1a<br><code>http:\/\/localhost:3000<\/code><\/li>\n\n\n\n<li><strong>Authorized redirect URIs<\/strong>\uff1a<br><code>http:\/\/localhost:3000\/api\/auth\/callback\/google<\/code><\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u5efa\u7acb\u5f8c\u8907\u88fd <strong>Client ID<\/strong> \u8207 <strong>Client Secret<\/strong><\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u9a57\u6536:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] \u624b\u908a\u6709 Client ID\uff08\u7d50\u5c3e <code>apps.googleusercontent.com<\/code>\uff09<\/li>\n\n\n\n<li>[ ] \u624b\u908a\u6709 Client Secret\uff08\u901a\u5e38\u4ee5 <code>GOCSPX-<\/code> \u958b\u982d\uff09<\/li>\n\n\n\n<li>[ ] Redirect URI \u5217\u8868\u4e2d\u6709 <code>\/api\/auth\/callback\/google<\/code><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%ad%a5%e9%a9%9f-2%ef%bc%9a%e7%92%b0%e5%a2%83%e8%ae%8a%e6%95%b8\">\u6b65\u9a5f 2\uff1a\u74b0\u5883\u8b8a\u6578<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>\u76ee\u7684<\/strong>\uff1a\u628a Google \u6191\u8b49\u8207 NextAuth \u8a2d\u5b9a\u5beb\u9032\u5c08\u6848\u3002<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">\u5728\u5c08\u6848\u6839\u76ee\u9304\u5efa\u7acb <strong><code>.env.local<\/code><\/strong>\uff08\u82e5\u5df2\u5b58\u5728\u5247\u8ffd\u52a0\uff09\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">AUTH_GOOGLE_ID=\u8cbc\u4e0a\u4f60\u7684 Client ID\nAUTH_GOOGLE_SECRET=\u8cbc\u4e0a\u4f60\u7684 Client Secret\n\nAUTH_SECRET=\u96a8\u6a5f\u5b57\u4e32\u81f3\u5c1132\u5b57\u5143\nNEXTAUTH_SECRET=\u540c\u4e0a\uff0c\u8207 AUTH_SECRET \u76f8\u540c\u5373\u53ef\n\nNEXTAUTH_URL=http:<span class=\"hljs-comment\">\/\/localhost:3000<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id=\"%e8%ae%8a%e6%95%b8%e5%88%a5%e5%90%8d%ef%bc%88%e6%93%87%e4%b8%80%e7%b5%84%e5%8d%b3%e5%8f%af%ef%bc%89\">\u8b8a\u6578\u5225\u540d\uff08\u64c7\u4e00\u7d44\u5373\u53ef\uff09<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u7528\u9014<\/th><th>\u540d\u7a31 A<\/th><th>\u540d\u7a31 B<\/th><\/tr><\/thead><tbody><tr><td>Client ID<\/td><td><code>AUTH_GOOGLE_ID<\/code><\/td><td><code>GOOGLE_CLIENT_ID<\/code><\/td><\/tr><tr><td>Client Secret<\/td><td><code>AUTH_GOOGLE_SECRET<\/code><\/td><td><code>GOOGLE_CLIENT_SECRET<\/code><\/td><\/tr><tr><td>Secret<\/td><td><code>AUTH_SECRET<\/code><\/td><td><code>NEXTAUTH_SECRET<\/code><\/td><\/tr><tr><td>\u7db2\u7ad9 URL<\/td><td><code>NEXTAUTH_URL<\/code><\/td><td><code>AUTH_URL<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u9a57\u6536:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] <code>.env.local<\/code> \u56db\u9805\u7686\u6709\u503c<\/li>\n\n\n\n<li>[ ] \u91cd\u555f dev server \u5f8c\uff0c\u7d42\u7aef\u6a5f<strong>\u6c92\u6709<\/strong> <code>[next-auth][warn][NO_SECRET]<\/code><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%ad%a5%e9%a9%9f-3%ef%bc%9a%e5%ae%89%e8%a3%9d-next-auth\">\u6b65\u9a5f 3\uff1a\u5b89\u88dd next-auth<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">npm install next-auth<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u9a57\u6536:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] <code>package.json<\/code> \u7684 <code>dependencies<\/code> \u51fa\u73fe <code>\"next-auth\"<\/code><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%ad%a5%e9%a9%9f-4%ef%bc%9a%e5%bb%ba%e7%ab%8b-lib-auth-env-ts\">\u6b65\u9a5f 4\uff1a\u5efa\u7acb <code>lib\/auth-env.ts<\/code><\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>\u76ee\u7684<\/strong>\uff1a\u96c6\u4e2d\u8b80\u53d6\u74b0\u5883\u8b8a\u6578\uff0c\u907f\u514d\u5728\u5404\u8655\u76f4\u63a5\u5b58\u53d6 <code>process.env<\/code>\u3002<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">\u5efa\u7acb <code>lib\/auth-env.ts<\/code>\uff0c\u8cbc\u4e0a\u4ee5\u4e0b\u5b8c\u6574\u5167\u5bb9\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getGoogleClientId<\/span>(<span class=\"hljs-params\"><\/span>): <span class=\"hljs-title\">string<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    process.env.GOOGLE_CLIENT_ID?.trim() ||\n    process.env.AUTH_GOOGLE_ID?.trim() ||\n    <span class=\"hljs-string\">\"\"<\/span>\n  );\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getGoogleClientSecret<\/span>(<span class=\"hljs-params\"><\/span>): <span class=\"hljs-title\">string<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    process.env.GOOGLE_CLIENT_SECRET?.trim() ||\n    process.env.AUTH_GOOGLE_SECRET?.trim() ||\n    <span class=\"hljs-string\">\"\"<\/span>\n  );\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getAuthSecret<\/span>(<span class=\"hljs-params\"><\/span>): <span class=\"hljs-title\">string<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    process.env.NEXTAUTH_SECRET?.trim() ||\n    process.env.AUTH_SECRET?.trim() ||\n    <span class=\"hljs-string\">\"\"<\/span>\n  );\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getAuthUrl<\/span>(<span class=\"hljs-params\"><\/span>): <span class=\"hljs-title\">string<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    process.env.NEXTAUTH_URL?.trim() ||\n    process.env.AUTH_URL?.trim() ||\n    <span class=\"hljs-string\">\"http:\/\/localhost:3000\"<\/span>\n  );\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">isAuthConfigured<\/span>(<span class=\"hljs-params\"><\/span>): <span class=\"hljs-title\">boolean<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-built_in\">Boolean<\/span>(\n    getGoogleClientId() &amp;&amp; getGoogleClientSecret() &amp;&amp; getAuthSecret()\n  );\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getAuthConfigError<\/span>(<span class=\"hljs-params\"><\/span>): <span class=\"hljs-title\">string<\/span> | <span class=\"hljs-title\">null<\/span> <\/span>{\n  <span class=\"hljs-keyword\">if<\/span> (!getGoogleClientId()) {\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">\"\u7f3a\u5c11 GOOGLE_CLIENT_ID \u6216 AUTH_GOOGLE_ID\"<\/span>;\n  }\n  <span class=\"hljs-keyword\">if<\/span> (!getGoogleClientSecret()) {\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">\"\u7f3a\u5c11 GOOGLE_CLIENT_SECRET \u6216 AUTH_GOOGLE_SECRET\"<\/span>;\n  }\n  <span class=\"hljs-keyword\">if<\/span> (!getAuthSecret()) {\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">\"\u7f3a\u5c11 NEXTAUTH_SECRET \u6216 AUTH_SECRET\"<\/span>;\n  }\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">null<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u9a57\u6536:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] \u6a94\u6848\u5b58\u5728\uff0c\u5c08\u6848\u53ef\u6b63\u5e38\u7de8\u8b6f\uff08<code>npm run dev<\/code> \u7121 import \u932f\u8aa4\uff09<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%ad%a5%e9%a9%9f-5%ef%bc%9a%e5%bb%ba%e7%ab%8b-lib-auth-options-ts\">\u6b65\u9a5f 5\uff1a\u5efa\u7acb <code>lib\/auth-options.ts<\/code><\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>\u76ee\u7684<\/strong>\uff1aNextAuth \u6838\u5fc3\u8a2d\u5b9a\u2014\u2014Google Provider\u3001Session callback\u3001\u81ea\u8a02\u767b\u5165\u9801\u8def\u5f91\u3002<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">\u5efa\u7acb <code>lib\/auth-options.ts<\/code>\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> type { NextAuthOptions } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next-auth\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> GoogleProvider <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next-auth\/providers\/google\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> {\n  getAuthSecret,\n  getAuthUrl,\n  getGoogleClientId,\n  getGoogleClientSecret,\n} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/lib\/auth-env\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> authOptions: NextAuthOptions = {\n  <span class=\"hljs-attr\">providers<\/span>: [\n    GoogleProvider({\n      <span class=\"hljs-attr\">clientId<\/span>: getGoogleClientId(),\n      <span class=\"hljs-attr\">clientSecret<\/span>: getGoogleClientSecret(),\n    }),\n  ],\n  <span class=\"hljs-attr\">pages<\/span>: {\n    <span class=\"hljs-attr\">signIn<\/span>: <span class=\"hljs-string\">\"\/login\"<\/span>,\n  },\n  <span class=\"hljs-attr\">callbacks<\/span>: {\n    session({ session, token }) {\n      <span class=\"hljs-keyword\">if<\/span> (session.user &amp;&amp; token.sub) {\n        session.user.id = token.sub;\n      }\n      <span class=\"hljs-keyword\">return<\/span> session;\n    },\n  },\n  <span class=\"hljs-attr\">secret<\/span>: getAuthSecret(),\n  ...(getAuthUrl() ? { <span class=\"hljs-attr\">url<\/span>: getAuthUrl() } : {}),\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u91cd\u9ede\u8aaa\u660e\uff1a<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>pages.signIn: \"\/login\"<\/code> \u2014 NextAuth \u9810\u8a2d\u767b\u5165\u9801\u6539\u70ba\u4f60\u7684 <code>\/login<\/code><\/li>\n\n\n\n<li><code>session<\/code> callback \u2014 \u628a Google \u4f7f\u7528\u8005\u552f\u4e00 ID\uff08<code>token.sub<\/code>\uff09\u5beb\u5165 <code>session.user.id<\/code>\uff0c\u5f8c\u7e8c\u7576\u8cc7\u6599\u5eab key \u4f7f\u7528<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u9a57\u6536:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] \u6a94\u6848\u5b58\u5728\uff0c\u7121 TypeScript \u932f\u8aa4<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%ad%a5%e9%a9%9f-6%ef%bc%9a%e5%bb%ba%e7%ab%8b-api-%e8%b7%af%e7%94%b1\">\u6b65\u9a5f 6\uff1a\u5efa\u7acb API \u8def\u7531<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>\u76ee\u7684<\/strong>\uff1a\u63d0\u4f9b NextAuth \u6240\u9700\u7684 HTTP \u7aef\u9ede\uff0c\u542b Google OAuth \u56de\u547c\u3002<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">\u5efa\u7acb\u76ee\u9304\u8207\u6a94\u6848 <code>app\/api\/auth\/[...nextauth]\/route.ts<\/code>\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> NextAuth <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next-auth\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { authOptions } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/lib\/auth-options\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> handler = NextAuth(authOptions);\n\n<span class=\"hljs-keyword\">export<\/span> { handler <span class=\"hljs-keyword\">as<\/span> GET, handler <span class=\"hljs-keyword\">as<\/span> POST };<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">\u6b64\u8def\u7531\u81ea\u52d5\u8655\u7406\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u8def\u5f91<\/th><th>\u7528\u9014<\/th><\/tr><\/thead><tbody><tr><td><code>\/api\/auth\/signin\/google<\/code><\/td><td>\u958b\u59cb Google \u767b\u5165<\/td><\/tr><tr><td><code>\/api\/auth\/callback\/google<\/code><\/td><td>Google \u6388\u6b0a\u5f8c\u56de\u547c<\/td><\/tr><tr><td><code>\/api\/auth\/session<\/code><\/td><td>\u8b80\u53d6\u76ee\u524d Session<\/td><\/tr><tr><td><code>\/api\/auth\/signout<\/code><\/td><td>\u767b\u51fa<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u9a57\u6536:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] \u700f\u89bd\u5668\u958b\u555f <code>http:\/\/localhost:3000\/api\/auth\/signin\/google<\/code><\/li>\n\n\n\n<li>[ ] \u80fd\u8df3\u8f49\u5230 Google \u767b\u5165\u9801\uff08\u6216 Google \u5e33\u865f\u9078\u64c7\u9801\uff09<\/li>\n\n\n\n<li>[ ] \u82e5\u51fa\u73fe <code>redirect_uri_mismatch<\/code> \u2192 \u56de\u5230\u6b65\u9a5f 1 \u6aa2\u67e5 Console \u7684 redirect URI<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%ad%a5%e9%a9%9f-7%ef%bc%9a%e5%bb%ba%e7%ab%8b-types-next-auth-d-ts\">\u6b65\u9a5f 7\uff1a\u5efa\u7acb <code>types\/next-auth.d.ts<\/code><\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>\u76ee\u7684<\/strong>\uff1aTypeScript \u9810\u8a2d\u7684 <code>session.user<\/code> \u6c92\u6709 <code>id<\/code>\uff0c\u9700\u64f4\u5145\u578b\u5225\u3002<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">\u5efa\u7acb <code>types\/next-auth.d.ts<\/code>\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">import type { DefaultSession } from <span class=\"hljs-string\">\"next-auth\"<\/span>;\n\n<span class=\"hljs-keyword\">declare<\/span> module <span class=\"hljs-string\">\"next-auth\"<\/span> {\n  <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">Session<\/span> <\/span>{\n    user: {\n      id: string;\n    } &amp; DefaultSession[<span class=\"hljs-string\">\"user\"<\/span>];\n  }\n}\n\n<span class=\"hljs-keyword\">declare<\/span> module <span class=\"hljs-string\">\"next-auth\/jwt\"<\/span> {\n  <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">JWT<\/span> <\/span>{\n    sub: string;\n  }\n}\n\nexport {};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u9a57\u6536:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] \u5f8c\u7e8c\u4f7f\u7528 <code>session.user.id<\/code> \u6642 TypeScript \u4e0d\u5831\u932f<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%ad%a5%e9%a9%9f-8%ef%bc%9a%e5%bb%ba%e7%ab%8b-lib-auth-ts\">\u6b65\u9a5f 8\uff1a\u5efa\u7acb <code>lib\/auth.ts<\/code><\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>\u76ee\u7684<\/strong>\uff1aServer \u7aef\u7d71\u4e00\u8b80\u53d6\u767b\u5165\u4f7f\u7528\u8005\u3002<strong>\u6240\u6709 Server Component \u8207 API Route \u90fd\u61c9\u547c\u53eb\u6b64\u6a94\u7684\u51fd\u5f0f\u3002<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">\u5efa\u7acb <code>lib\/auth.ts<\/code>\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { getServerSession } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next-auth\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { authOptions } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/lib\/auth-options\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getSessionUser<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> session = <span class=\"hljs-keyword\">await<\/span> getServerSession(authOptions);\n  <span class=\"hljs-keyword\">if<\/span> (!session?.user?.id) <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">null<\/span>;\n\n  <span class=\"hljs-keyword\">return<\/span> {\n    <span class=\"hljs-attr\">id<\/span>: session.user.id,\n    <span class=\"hljs-attr\">name<\/span>: session.user.name ?? <span class=\"hljs-string\">\"\"<\/span>,\n    <span class=\"hljs-attr\">email<\/span>: session.user.email ?? <span class=\"hljs-string\">\"\"<\/span>,\n    <span class=\"hljs-attr\">image<\/span>: session.user.image ?? <span class=\"hljs-string\">\"\"<\/span>,\n  };\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">isAdminEmail<\/span>(<span class=\"hljs-params\">email: string<\/span>): <span class=\"hljs-title\">boolean<\/span> <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> admins = (process.env.ADMIN_EMAILS ?? <span class=\"hljs-string\">\"\"<\/span>)\n    .split(<span class=\"hljs-string\">\",\"<\/span>)\n    .map(<span class=\"hljs-function\">(<span class=\"hljs-params\">item<\/span>) =&gt;<\/span> item.trim().toLowerCase())\n    .filter(<span class=\"hljs-built_in\">Boolean<\/span>);\n\n  <span class=\"hljs-keyword\">return<\/span> admins.includes(email.toLowerCase());\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u56de\u50b3\u503c\uff1a<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u6b04\u4f4d<\/th><th>\u4f86\u6e90<\/th><th>\u7528\u9014<\/th><\/tr><\/thead><tbody><tr><td><code>id<\/code><\/td><td>Google <code>sub<\/code><\/td><td>\u4f7f\u7528\u8005\u552f\u4e00 key\uff0c\u5b58\u8cc7\u6599\u5eab \/ JSON<\/td><\/tr><tr><td><code>name<\/code><\/td><td>Google \u59d3\u540d<\/td><td>\u986f\u793a\u7528<\/td><\/tr><tr><td><code>email<\/code><\/td><td>Google Email<\/td><td>\u986f\u793a\u3001\u7ba1\u7406\u54e1\u767d\u540d\u55ae<\/td><\/tr><tr><td><code>image<\/code><\/td><td>Google \u982d\u50cf URL<\/td><td>\u53ef\u9078\u986f\u793a<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">\u672a\u767b\u5165\u6642 <code>getSessionUser()<\/code> \u56de\u50b3 <code>null<\/code>\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u9a57\u6536:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] \u6a94\u6848\u5b58\u5728\uff0c\u53ef\u88ab\u5176\u4ed6\u6a21\u7d44 import<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%ad%a5%e9%a9%9f-9%ef%bc%9asessionprovider-%e8%88%87-layout\">\u6b65\u9a5f 9\uff1aSessionProvider \u8207 layout<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>\u76ee\u7684<\/strong>\uff1aClient Component \u8981\u4f7f\u7528 <code>signIn<\/code> \/ <code>signOut<\/code> \/ <code>useSession<\/code>\uff0c\u6839 layout \u5fc5\u9808\u5305\u4e00\u5c64 Provider\u3002<\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"9-1-%e5%bb%ba%e7%ab%8b-provider\">9.1 \u5efa\u7acb Provider<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u5efa\u7acb <code>components\/providers\/session-provider.tsx<\/code>\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-string\">\"use client\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { SessionProvider } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next-auth\/react\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">AuthProvider<\/span>(<span class=\"hljs-params\">{ children }: { children: React.ReactNode }<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">SessionProvider<\/span>&gt;<\/span>{children}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">SessionProvider<\/span>&gt;<\/span><\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id=\"9-2-%e4%bf%ae%e6%94%b9-app-layout-tsx\">9.2 \u4fee\u6539 <code>app\/layout.tsx<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u5728 <code>&lt;body&gt;<\/code> \u5167\u7528 <code>AuthProvider<\/code> \u5305\u4f4f\u6240\u6709\u5167\u5bb9\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { AuthProvider } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/components\/providers\/session-provider\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">RootLayout<\/span>(<span class=\"hljs-params\">{\n  children,\n}: {\n  children: React.ReactNode;\n}<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">html<\/span> <span class=\"hljs-attr\">lang<\/span>=<span class=\"hljs-string\">\"zh-TW\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">AuthProvider<\/span>&gt;<\/span>\n          {children}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">AuthProvider<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">html<\/span>&gt;<\/span><\/span>\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">\u82e5\u4f60\u5df2\u6709 header \/ main \u7d50\u69cb\uff0c\u653e\u5728 Provider \u88e1\u9762\u5373\u53ef\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">AuthProvider<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">SiteHeader<\/span> \/&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span>&gt;<\/span>{children}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">AuthProvider<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u9a57\u6536:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] \u9801\u9762\u6b63\u5e38\u6e32\u67d3\uff0c\u7121 <code>useSession must be wrapped in SessionProvider<\/code> \u932f\u8aa4<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%ad%a5%e9%a9%9f-10%ef%bc%9a%e7%99%bb%e5%85%a5%e9%a0%81\">\u6b65\u9a5f 10\uff1a\u767b\u5165\u9801<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>\u76ee\u7684<\/strong>\uff1a\u81ea\u8a02\u767b\u5165 UI\uff0c\u4e26\u6b63\u78ba\u50b3\u905e <code>callbackUrl<\/code> \u7d66 Google \u767b\u5165\u6d41\u7a0b\u3002<\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"10-1-%e5%bb%ba%e7%ab%8b-components-login-form-tsx\">10.1 \u5efa\u7acb <code>components\/login-form.tsx<\/code><\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-string\">\"use client\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { signIn } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next-auth\/react\"<\/span>;\n\ntype LoginFormProps = {\n  <span class=\"hljs-attr\">callbackUrl<\/span>: string;\n  authConfigured: boolean;\n  configError: string | <span class=\"hljs-literal\">null<\/span>;\n  oauthError?: string;\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">LoginForm<\/span>(<span class=\"hljs-params\">{\n  callbackUrl,\n  authConfigured,\n  configError,\n  oauthError,\n}: LoginFormProps<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\u767b\u5165<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\u8acb\u4f7f\u7528 Google \u5e33\u865f\u767b\u5165<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n\n      {!authConfigured &amp;&amp; configError ? (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>Google SSO \u5c1a\u672a\u8a2d\u5b9a\uff1a{configError}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      ) : null}\n\n      {oauthError ? (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\u767b\u5165\u5931\u6557\uff0c\u8acb\u78ba\u8a8d Google OAuth \u8a2d\u5b9a\u8207 NEXTAUTH_URL\u3002<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      ) : null}\n\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n        <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"button\"<\/span>\n        <span class=\"hljs-attr\">disabled<\/span>=<span class=\"hljs-string\">{!authConfigured}<\/span>\n        <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> signIn(\"google\", { callbackUrl })}\n      &gt;\n        \u4f7f\u7528 Google \u767b\u5165\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id=\"10-2-%e5%bb%ba%e7%ab%8b-app-login-page-tsx\">10.2 \u5efa\u7acb <code>app\/login\/page.tsx<\/code><\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { redirect } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/navigation\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { LoginForm } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/components\/login-form\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { getAuthConfigError, isAuthConfigured } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/lib\/auth-env\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { getSessionUser } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/lib\/auth\"<\/span>;\n\ntype LoginPageProps = {\n  <span class=\"hljs-attr\">searchParams<\/span>: <span class=\"hljs-built_in\">Promise<\/span>&lt;{ callbackUrl?: string; error?: string }&gt;;\n};\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getSafeCallbackUrl<\/span>(<span class=\"hljs-params\">raw?: string<\/span>): <span class=\"hljs-title\">string<\/span> <\/span>{\n  <span class=\"hljs-keyword\">if<\/span> (!raw) <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">\"\/\"<\/span>;\n\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> decoded = <span class=\"hljs-built_in\">decodeURIComponent<\/span>(raw);\n    <span class=\"hljs-keyword\">const<\/span> url = decoded.startsWith(<span class=\"hljs-string\">\"http\"<\/span>)\n      ? <span class=\"hljs-keyword\">new<\/span> URL(decoded)\n      : <span class=\"hljs-keyword\">new<\/span> URL(decoded, <span class=\"hljs-string\">\"http:\/\/localhost\"<\/span>);\n\n    <span class=\"hljs-keyword\">if<\/span> (url.pathname.startsWith(<span class=\"hljs-string\">\"\/login\"<\/span>)) <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">\"\/\"<\/span>;\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">`<span class=\"hljs-subst\">${url.pathname}<\/span><span class=\"hljs-subst\">${url.search}<\/span>`<\/span>;\n  } <span class=\"hljs-keyword\">catch<\/span> {\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">\"\/\"<\/span>;\n  }\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">LoginPage<\/span>(<span class=\"hljs-params\">{ searchParams }: LoginPageProps<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> params = <span class=\"hljs-keyword\">await<\/span> searchParams;\n  <span class=\"hljs-keyword\">const<\/span> callbackUrl = getSafeCallbackUrl(params.callbackUrl);\n\n  <span class=\"hljs-keyword\">const<\/span> user = <span class=\"hljs-keyword\">await<\/span> getSessionUser();\n  <span class=\"hljs-keyword\">if<\/span> (user) {\n    redirect(callbackUrl);\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">LoginForm<\/span>\n      <span class=\"hljs-attr\">callbackUrl<\/span>=<span class=\"hljs-string\">{callbackUrl}<\/span>\n      <span class=\"hljs-attr\">authConfigured<\/span>=<span class=\"hljs-string\">{isAuthConfigured()}<\/span>\n      <span class=\"hljs-attr\">configError<\/span>=<span class=\"hljs-string\">{getAuthConfigError()}<\/span>\n      <span class=\"hljs-attr\">oauthError<\/span>=<span class=\"hljs-string\">{params.error}<\/span>\n    \/&gt;<\/span><\/span>\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong><code>getSafeCallbackUrl<\/code> \u505a\u4e86\u4ec0\u9ebc\uff1a<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u89e3\u6790 <code>?callbackUrl=<\/code> \u53c3\u6578<\/li>\n\n\n\n<li>\u53ea\u5141\u8a31\u7ad9\u5167\u8def\u5f91\uff0c\u9632\u6b62 open redirect \u653b\u64ca<\/li>\n\n\n\n<li>\u907f\u514d <code>callbackUrl=\/login<\/code> \u9020\u6210\u7121\u9650\u8ff4\u5708<\/li>\n\n\n\n<li>\u5df2\u767b\u5165\u4f7f\u7528\u8005\u9020\u8a2a <code>\/login<\/code> \u6642\uff0c\u76f4\u63a5 <code>redirect(callbackUrl)<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u9a57\u6536:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] \u958b\u555f <code>http:\/\/localhost:3000\/login<\/code> \u80fd\u770b\u5230\u767b\u5165\u6309\u9215<\/li>\n\n\n\n<li>[ ] \u9ede\u300cGoogle \u767b\u5165\u300d\u2192 Google \u6388\u6b0a \u2192 \u6210\u529f\u5f8c\u56de\u5230\u9996\u9801 <code>\/<\/code><\/li>\n\n\n\n<li>[ ] \u958b\u555f <code>http:\/\/localhost:3000\/login?callbackUrl=\/profile<\/code> \u2192 \u767b\u5165\u5f8c\u8df3\u5230 <code>\/profile<\/code><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%ad%a5%e9%a9%9f-11%ef%bc%9a%e5%b0%8e%e8%a6%bd%e5%88%97%e7%99%bb%e5%85%a5-%e7%99%bb%e5%87%ba\">\u6b65\u9a5f 11\uff1a\u5c0e\u89bd\u5217\u767b\u5165\/\u767b\u51fa<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">\u52a0\u4e0aLayout\u5f8c\u5c31\u53ef\u4ee5\u5be6\u73fe\u5168\u7ad9\u4efb\u610f\u9801\u9762\u53ef\u767b\u5165\u3001\u767b\u51fa\u3001\u986f\u793a Email\u3002<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">\u5efa\u7acb <code>components\/auth-button.tsx<\/code>\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-string\">\"use client\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { signIn, signOut, useSession } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next-auth\/react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Link <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/link\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">AuthButton<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { <span class=\"hljs-attr\">data<\/span>: session, status } = useSession();\n\n  <span class=\"hljs-keyword\">if<\/span> (status === <span class=\"hljs-string\">\"loading\"<\/span>) {\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span>&gt;<\/span>\u8f09\u5165\u4e2d...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span><\/span>;\n  }\n\n  <span class=\"hljs-keyword\">if<\/span> (!session?.user) {\n    <span class=\"hljs-keyword\">return<\/span> (\n      <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"button\"<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> signIn(\"google\")}&gt;\n        Google \u767b\u5165\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span><\/span>\n    );\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/profile\"<\/span>&gt;<\/span>\u500b\u4eba\u8cc7\u6599<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span>&gt;<\/span>{session.user.email}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"button\"<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> signOut({ callbackUrl: \"\/\" })}&gt;\n        \u767b\u51fa\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">\u5728 header \u5143\u4ef6\u4e2d\u5f15\u5165\uff08\u7bc4\u4f8b\uff09\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { AuthButton } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/components\/auth-button\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">SiteHeader<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">nav<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">AuthButton<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">nav<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span><\/span>\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id=\"server-vs-client-%e8%ae%80%e5%8f%96-session\">Server vs Client \u8b80\u53d6 Session<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u57f7\u884c\u4f4d\u7f6e<\/th><th>\u7528\u6cd5<\/th><th>\u5834\u666f<\/th><\/tr><\/thead><tbody><tr><td>Server Component \/ API<\/td><td><code>await getSessionUser()<\/code><\/td><td>\u9801\u9762\u4fdd\u8b77\u3001\u5f8c\u7aef\u908f\u8f2f<\/td><\/tr><tr><td>Client Component<\/td><td><code>useSession()<\/code><\/td><td>\u6309\u9215\u3001\u5373\u6642 UI<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u4e0d\u8981\u6df7\u7528<\/strong>\uff1aServer \u7aef\u4e0d\u8981\u7528 <code>useSession()<\/code>\uff1bClient \u7aef\u4e0d\u8981\u7528 <code>getServerSession()<\/code>\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u9a57\u6536:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] \u672a\u767b\u5165\u6642 header \u986f\u793a\u300cGoogle \u767b\u5165\u300d<\/li>\n\n\n\n<li>[ ] \u767b\u5165\u5f8c\u986f\u793a Email \u8207\u300c\u767b\u51fa\u300d<\/li>\n\n\n\n<li>[ ] \u767b\u51fa\u5f8c\u56de\u5230\u9996\u9801\uff0cSession \u6e05\u9664<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%ad%a5%e9%a9%9f-12%ef%bc%9a%e4%bf%9d%e8%ad%b7%e9%a0%81%e9%9d%a2%ef%bc%88%e6%9c%aa%e7%99%bb%e5%85%a5%e5%b0%8e%e5%9b%9e%ef%bc%89\">\u6b65\u9a5f 12\uff1a\u4fdd\u8b77\u9801\u9762\uff08\u672a\u767b\u5165\u5c0e\u56de\uff09<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>\u76ee\u7684<\/strong>\uff1a\u7279\u5b9a\u9801\u9762\u53ea\u5141\u8a31\u767b\u5165\u4f7f\u7528\u8005\u5b58\u53d6\uff1b\u672a\u767b\u5165\u8005\u9001\u53bb\u767b\u5165\u9801\uff0c\u4e26\u8a18\u4f4f\u539f\u672c\u8981\u53bb\u7684\u8def\u3002<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">\u5728\u4efb\u4f55\u9700\u8981\u767b\u5165\u7684 <strong>Server Component \u9801\u9762<\/strong> \u6700\u4e0a\u65b9\u52a0\u5165\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { redirect } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/navigation\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { getSessionUser } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/lib\/auth\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">ProfilePage<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> user = <span class=\"hljs-keyword\">await<\/span> getSessionUser();\n  <span class=\"hljs-keyword\">if<\/span> (!user) {\n    redirect(<span class=\"hljs-string\">\"\/login?callbackUrl=\/profile\"<\/span>);\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\u500b\u4eba\u8cc7\u6599<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>{user.name}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>{user.email}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u898f\u5247\uff1a<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>callbackUrl<\/code> \u586b<strong>\u6b64\u9801\u9762\u7684\u8def\u5f91<\/strong>\uff08\u4ee5 <code>\/<\/code> \u958b\u982d\uff09<\/li>\n\n\n\n<li>\u767b\u5165\u6210\u529f\u5f8c\u4f7f\u7528\u8005\u6703\u81ea\u52d5\u56de\u5230\u9019\u88e1<\/li>\n\n\n\n<li>\u7528 <code>user.id<\/code> \u4f5c\u70ba\u8cc7\u6599\u5eab \/ \u5132\u5b58\u7684\u4f7f\u7528\u8005 key<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%e9%80%b2%e9%9a%8e%ef%bc%9a%e7%99%bb%e5%85%a5%e5%be%8c%e9%82%84%e9%9c%80%e8%a3%9c%e8%b3%87%e6%96%99\">\u9032\u968e\uff1a\u767b\u5165\u5f8c\u9084\u9700\u88dc\u8cc7\u6599<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u82e5\u5df2\u767b\u5165\u4f46\u500b\u4eba\u8cc7\u6599\u672a\u5b8c\u6210\uff0c\u7528 <strong><code>returnUrl<\/code><\/strong>\uff08\u4e0d\u662f <code>callbackUrl<\/code>\uff09\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> user = <span class=\"hljs-keyword\">await<\/span> getSessionUser();\n<span class=\"hljs-keyword\">if<\/span> (!user) redirect(<span class=\"hljs-string\">\"\/login?callbackUrl=\/3d-printer\/apply\"<\/span>);\n\n<span class=\"hljs-keyword\">const<\/span> profile = <span class=\"hljs-keyword\">await<\/span> getUserProfile(user.id);\n<span class=\"hljs-keyword\">if<\/span> (!isProfileComplete(profile)) {\n  redirect(<span class=\"hljs-string\">\"\/profile?returnUrl=\/3d-printer\/apply\"<\/span>);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u53c3\u6578<\/th><th>\u6642\u6a5f<\/th><\/tr><\/thead><tbody><tr><td><code>callbackUrl<\/code><\/td><td><strong>\u672a\u767b\u5165<\/strong> \u2192 \u767b\u5165\u5f8c\u56de\u53bb<\/td><\/tr><tr><td><code>returnUrl<\/code><\/td><td><strong>\u5df2\u767b\u5165<\/strong>\u4f46\u8cc7\u6599\u4e0d\u8db3 \u2192 \u586b\u5b8c\u56de\u53bb<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u9a57\u6536:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] \u672a\u767b\u5165\u76f4\u63a5\u958b\u555f\u53d7\u4fdd\u8b77\u9801 \u2192 \u81ea\u52d5\u8df3\u5230 <code>\/login?callbackUrl=...<\/code><\/li>\n\n\n\n<li>[ ] \u767b\u5165\u5f8c\u81ea\u52d5\u56de\u5230\u53d7\u4fdd\u8b77\u9801\uff0c\u80fd\u770b\u5230\u5167\u5bb9<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%ad%a5%e9%a9%9f-13%ef%bc%9a%e4%bf%9d%e8%ad%b7-api-route\">\u6b65\u9a5f 13\uff1a\u4fdd\u8b77 API Route<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>\u76ee\u7684<\/strong>\uff1aAPI \u4e0d\u63a5\u53d7\u672a\u767b\u5165\u8acb\u6c42\uff0c\u56de\u50b3 401 JSON\uff08API \u7121\u6cd5\u4f7f\u7528 <code>redirect()<\/code>\uff09\u3002<\/p>\n<\/blockquote>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { NextResponse } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/server\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { getSessionUser } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/lib\/auth\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">GET<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> user = <span class=\"hljs-keyword\">await<\/span> getSessionUser();\n  <span class=\"hljs-keyword\">if<\/span> (!user) {\n    <span class=\"hljs-keyword\">return<\/span> NextResponse.json({ <span class=\"hljs-attr\">error<\/span>: <span class=\"hljs-string\">\"\u8acb\u5148\u767b\u5165\"<\/span> }, { <span class=\"hljs-attr\">status<\/span>: <span class=\"hljs-number\">401<\/span> });\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> NextResponse.json({\n    <span class=\"hljs-attr\">userId<\/span>: user.id,\n    <span class=\"hljs-attr\">email<\/span>: user.email,\n  });\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Client \u7aef fetch \u82e5\u6536\u5230 401\uff0c\u53ef\u5c0e\u5411\u767b\u5165\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> res = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/your-endpoint\"<\/span>);\n<span class=\"hljs-keyword\">if<\/span> (res.status === <span class=\"hljs-number\">401<\/span>) {\n  <span class=\"hljs-built_in\">window<\/span>.location.href = <span class=\"hljs-string\">`\/login?callbackUrl=<span class=\"hljs-subst\">${<span class=\"hljs-built_in\">encodeURIComponent<\/span>(<span class=\"hljs-built_in\">window<\/span>.location.pathname)}<\/span>`<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u9a57\u6536:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] \u672a\u767b\u5165\u547c\u53eb API \u2192 HTTP 401<\/li>\n\n\n\n<li>[ ] \u767b\u5165\u5f8c\u547c\u53eb API \u2192 \u6b63\u5e38\u56de\u50b3\u8cc7\u6599<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%ad%a5%e9%a9%9f-14%ef%bc%9a%e7%ab%af%e5%88%b0%e7%ab%af%e9%a9%97%e6%94%b6\">\u6b65\u9a5f 14\uff1a\u7aef\u5230\u7aef\u9a57\u6536<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u5168\u90e8\u5b8c\u6210\u5f8c\uff0c\u4f9d\u5e8f\u57f7\u884c\u4ee5\u4e0b\u6e2c\u8a66\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">\u6e2c\u8a66 A\uff1a\u57fa\u672c\u767b\u5165\n  <span class=\"hljs-number\">1.<\/span> \u958b\u7121\u75d5\u8996\u7a97 \u2192 http:<span class=\"hljs-comment\">\/\/localhost:3000\/login<\/span>\n  <span class=\"hljs-number\">2.<\/span> \u9ede Google \u767b\u5165 \u2192 \u9078\u5e33\u865f \u2192 \u540c\u610f\n  <span class=\"hljs-number\">3.<\/span> \u9810\u671f\uff1a\u8df3\u56de\u9996\u9801\uff0cheader \u986f\u793a Email\n\n\u6e2c\u8a66 B\uff1acallbackUrl\n  <span class=\"hljs-number\">1.<\/span> \u7121\u75d5\u8996\u7a97 \u2192 http:<span class=\"hljs-comment\">\/\/localhost:3000\/login?callbackUrl=\/profile<\/span>\n  <span class=\"hljs-number\">2.<\/span> \u767b\u5165\n  <span class=\"hljs-number\">3.<\/span> \u9810\u671f\uff1a\u767b\u5165\u5f8c\u5728 \/profile\uff0c\u800c\u975e\u9996\u9801\n\n\u6e2c\u8a66 C\uff1a\u53d7\u4fdd\u8b77\u9801\u9762\n  <span class=\"hljs-number\">1.<\/span> \u7121\u75d5\u8996\u7a97 \u2192 \u76f4\u63a5\u958b\u555f\u53d7\u4fdd\u8b77\u9801\uff08\u5982 \/profile\uff09\n  <span class=\"hljs-number\">2.<\/span> \u9810\u671f\uff1a\u81ea\u52d5\u8df3\u5230 \/login?callbackUrl=\/profile\n  <span class=\"hljs-number\">3.<\/span> \u767b\u5165\u5f8c\u56de\u5230 \/profile\n\n\u6e2c\u8a66 D\uff1a\u767b\u51fa\n  <span class=\"hljs-number\">1.<\/span> \u5df2\u767b\u5165\u72c0\u614b\u9ede\u300c\u767b\u51fa\u300d\n  <span class=\"hljs-number\">2.<\/span> \u9810\u671f\uff1a\u56de\u9996\u9801\uff0c\u518d\u958b\u53d7\u4fdd\u8b77\u9801\u9700\u91cd\u65b0\u767b\u5165\n\n\u6e2c\u8a66 E\uff1aSession \u6301\u4e45\n  <span class=\"hljs-number\">1.<\/span> \u767b\u5165\u5f8c\u91cd\u65b0\u6574\u7406\u9801\u9762\n  <span class=\"hljs-number\">2.<\/span> \u9810\u671f\uff1a\u4ecd\u4fdd\u6301\u767b\u5165\u72c0\u614b\n\n\u6e2c\u8a66 F\uff1aAPI\n  <span class=\"hljs-number\">1.<\/span> \u672a\u767b\u5165 fetch \u53d7\u4fdd\u8b77 API \u2192 <span class=\"hljs-number\">401<\/span>\n  <span class=\"hljs-number\">2.<\/span> \u767b\u5165\u5f8c fetch \u2192 <span class=\"hljs-number\">200<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">\u5168\u90e8\u901a\u904e \u2192 <strong>Google SSO \u6574\u5408\u5b8c\u6210\u3002<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%ad%a3%e5%bc%8f%e7%92%b0%e5%a2%83%e9%83%a8%e7%bd%b2\">\u6b63\u5f0f\u74b0\u5883\u90e8\u7f72<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Google Console<\/strong> \u8ffd\u52a0\u6b63\u5f0f\u7db2\u57df\uff1a<\/li>\n<\/ol>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">   https:<span class=\"hljs-comment\">\/\/your-domain.com                          \u2190 JavaScript origins<\/span>\n   https:<span class=\"hljs-comment\">\/\/your-domain.com\/api\/auth\/callback\/google \u2190 redirect URIs<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li><strong>\u4e3b\u6a5f\u74b0\u5883\u8b8a\u6578<\/strong>\uff1a<\/li>\n<\/ol>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">   NEXTAUTH_URL=https:<span class=\"hljs-comment\">\/\/your-domain.com<\/span>\n   AUTH_GOOGLE_ID=...\n   AUTH_GOOGLE_SECRET=...\n   NEXTAUTH_SECRET=...<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<ol start=\"3\" class=\"wp-block-list\">\n<li>OAuth \u540c\u610f\u756b\u9762\u82e5\u8981\u5c0d\u5916\u516c\u958b \u2192 \u63d0\u4ea4 Google \u5be9\u6838<\/li>\n\n\n\n<li>\u91cd\u65b0\u90e8\u7f72\u5f8c\u57f7\u884c\u300c\u6b65\u9a5f 14\u300d\u6e2c\u8a66\uff08\u6539\u7528\u6b63\u5f0f\u7db2\u5740\uff09<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e7%96%91%e9%9b%a3%e6%8e%92%e8%a7%a3\">\u7591\u96e3\u6392\u89e3<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u73fe\u8c61<\/th><th>\u539f\u56e0<\/th><th>\u89e3\u6cd5<\/th><\/tr><\/thead><tbody><tr><td><code>client_id is required<\/code><\/td><td>\u74b0\u5883\u8b8a\u6578\u672a\u8f09\u5165<\/td><td>\u78ba\u8a8d <code>.env.local<\/code> \u5b58\u5728\uff1b\u91cd\u555f dev server\uff1b\u4e0d\u8981\u540c\u6642\u653e\u5169\u7d44\u885d\u7a81\u7684 Client ID<\/td><\/tr><tr><td><code>redirect_uri_mismatch<\/code><\/td><td>Console \u672a\u767b\u8a18 URI<\/td><td>\u52a0\u5165 <code>http:\/\/localhost:3000\/api\/auth\/callback\/google<\/code><\/td><\/tr><tr><td><code>Access blocked<\/code><\/td><td>OAuth \u6e2c\u8a66\u6a21\u5f0f<\/td><td>Consent screen \u2192 Test users \u52a0\u5165\u4f60\u7684 Gmail<\/td><\/tr><tr><td><code>[next-auth][warn][NO_SECRET]<\/code><\/td><td>\u7f3a\u5c11 secret<\/td><td>\u8a2d\u5b9a <code>NEXTAUTH_SECRET<\/code> \u6216 <code>AUTH_SECRET<\/code><\/td><\/tr><tr><td><code>[next-auth][warn][NEXTAUTH_URL]<\/code><\/td><td>\u7f3a\u5c11\u6216\u8a2d\u932f URL<\/td><td>\u8a2d\u70ba <code>http:\/\/localhost:3000<\/code>\uff08\u4e0d\u542b callback \u8def\u5f91\uff09<\/td><\/tr><tr><td>\u767b\u5165\u6210\u529f\u4f46\u56de\u9996\u9801<\/td><td>\u672a\u50b3 callbackUrl<\/td><td>\u53d7\u4fdd\u8b77\u9801 redirect \u6642\u5e36 <code>?callbackUrl=\/your-path<\/code><\/td><\/tr><tr><td>\u767b\u5165\u5f8c\u7121 <code>user.id<\/code><\/td><td>callback \/ \u578b\u5225\u672a\u8a2d<\/td><td>\u78ba\u8a8d <code>auth-options.ts<\/code> session callback \u8207 <code>next-auth.d.ts<\/code><\/td><\/tr><tr><td><code>useSession must be wrapped...<\/code><\/td><td>\u7f3a Provider<\/td><td>\u78ba\u8a8d <code>layout.tsx<\/code> \u6709\u5305 <code>&lt;AuthProvider&gt;<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e5%8f%83%e8%80%83%e9%80%a3%e7%b5%90\">\u53c3\u8003\u9023\u7d50<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/next-auth.js.org\/\">NextAuth.js<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/next-auth.js.org\/providers\/google\">NextAuth Google Provider<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developers.google.com\/identity\/protocols\/oauth2\">Google OAuth 2.0<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>\u9069\u7528\u74b0\u5883\uff1aNext.js 14+ App Router\u3001Server action\u3001NextAuth.js v4\u3001TypeScript\u76ee\u6a19\uff1a\u7167\u672c\u6587\u4ef6\u5f9e\u96f6\u5b8c\u6210 Google \u55ae\u4e00\u767b\u5165\uff08SSO\uff09\uff0c\u542b\u767b\u5165\u9801\u3001Session \u8b80\u53d6\u3001\u672a\u767b\u5165\u5c0e\u56de\u3001\u767b\u51fa\u3002\u6700\u5f8c\u66f4\u65b0\u65e5\u671f\uff1a06\/14\/2026 \uff5c \u6700\u5f8c\u66f4\u65b0\u4eba\u54e1\uff1a\u9673\u6cd3\u6bd3 \u5b8c\u6210\u5f8c\u7684\u6a94\u6848\u7d50\u69cb \u5148\u4e86\u89e3\u6d41\u7a0b \u5f9e\u4f7f\u7528\u8005\u9801\u9762\u5230\u5b8c\u6210google sso\u5927\u81f4\u53ef\u4ee5\u5206\u70ba\u4ee5\u4e0b\u5e7e\u500b\u6b65\u9a5f\uff1a \u6b65\u9a5f \u767c\u751f\u4ec0\u9ebc\u4e8b \u2460 \u4f7f\u7528\u8005\u9020\u8a2a\u53d7\u4fdd\u8b77\u9801 \u2192 Server \u7528 getSessionUser() \u6aa2\u67e5 \u2192 \u672a\u767b\u5165\u5247 redirect(&#8220;\/login?callbackUrl=&#8230;&#8221;) \u2461 \u767b\u5165\u9801\u986f\u793a\u300cGoogle \u767b\u5165\u300d\u2192 \u547c\u53eb signIn(&#8220;google&#8221;, { callbackUrl }) \u2462 \u700f\u89bd\u5668\u8df3\u81f3 Google \u6388\u6b0a\u9801 \u2463 \u4f7f\u7528\u8005\u540c\u610f \u2192 Google \u5c0e\u5411 \/api\/auth\/callback\/google \u2464 NextAuth \u63db\u53d6 token\uff0c\u5beb\u5165 Session Cookie \u2465 \u81ea\u52d5\u8df3\u56de callbackUrl\uff08\u767b\u5165\u524d\u8981\u53bb\u7684\u9801\u9762\uff09 \u2466 [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[15],"tags":[],"class_list":["post-492","post","type-post","status-publish","format-standard","hentry","category-front-end"],"views":45,"_links":{"self":[{"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/posts\/492","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/comments?post=492"}],"version-history":[{"count":12,"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/posts\/492\/revisions"}],"predecessor-version":[{"id":654,"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/posts\/492\/revisions\/654"}],"wp:attachment":[{"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/media?parent=492"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/categories?post=492"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/tags?post=492"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}