{"id":10239,"date":"2026-09-14T07:39:52","date_gmt":"2026-09-13T22:39:52","guid":{"rendered":"https:\/\/www.freelifemakers.org\/wordpress\/?p=10239"},"modified":"2026-09-15T13:32:58","modified_gmt":"2026-09-15T04:32:58","slug":"nextjs-sns-server-30","status":"publish","type":"post","link":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/09\/14\/nextjs-sns-server-30\/","title":{"rendered":"[nextjs]SNS Server-30(myapp40)\u00a0"},"content":{"rendered":"\n<p>\ud83d\udc49\ud83c\udffb \ud68c\uc6d0\uac00\uc785 \ubc0f \ub85c\uadf8\uc778\uacfc \uad00\ub828\ub41c \uae30\ub2a5\uc785\ub2c8\ub2e4.<br>These are features related to sign-up and login.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb myapp40\uc5d0\uc11c\ub294 \uae30\uc874 \ub85c\uadf8\uc778 \ubcf4\uc548\uc744 \uc5c5\uadf8\ub808\uc774\ub4dc\ud569\ub2c8\ub2e4.<br>myapp40 is upgrading its existing login security.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \uae30\uc874 access token\uc678\uc5d0 refresh token\uc744 \ucd94\uac00\ud558\uace0 session \ud14c\uc774\ube14\uc5d0 \uc800\uc7a5\ud569\ub2c8\ub2e4.<br>In addition to the existing access token, a refresh token is added and stored in the session table.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb access token\uc740 15\ubd84 refresh token\uc740 7\uc77c\ub85c \uc124\uc815\ud558\uace0 \ub85c\uadf8\uc778\ud558\uba74 access token\uacfc refresh token\uc744 \ubc1c\uae09\ud558\uace0 session \ud14c\uc774\ube14\uc5d0 \uc800\uc7a5\ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \ub85c\uadf8\uc778\ud55c \uc0c1\ud0dc\uc5d0\uc11c access token\uc774 \ub9cc\ub8cc\ub418\uba74(15\ubd84) session\ud14c\uc774\ube14\uc5d0 refresh token(7\uc77c)\uc774 \uc788\ub294\uc9c0 \ud655\uc778\ud558\uace0 access token\uc744 \uc7ac\ubc1c\uae09\ud569\ub2c8\ub2e4.<br>If the access token expires (after 15 minutes) while logged in, the system checks the session table for a refresh token (valid for 7 days) and issues a new access token.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb  \uc790\ubc14\uc2a4\ud06c\ub9bd\ud2b8\uc5d0\uc11c\ub294 \ucfe0\ud0a4\ub97c \uc77d\uc744 \uc218 \uc5c6\ub3c4\ub85d \uc124\uc815\ud558\uace0(httpOnly: true) \ud504\ub85c\ub355\uc158\ubaa8\ub4dc\uc5d0\uc11c https\ub9cc \ud5c8\uc6a9\ud558\uace0(secure: IS_PROD) \ub2e4\ub978 \uc0ac\uc774\ud2b8\uc5d0\uc11c \uae00\uc4f0\uae30\ub97c \ub9c9\ub294 \uc124\uc815\uc744 \ucd94\uac00\ud569\ub2c8\ub2e4.(sameSite: &#8216;lax&#8217;)<br>In JavaScript, configure settings to prevent cookies from being read (<code>httpOnly: true<\/code>), allow only HTTPS in production mode (<code>secure: IS_PROD<\/code>), and add a setting to block write operations from other sites (<code>sameSite: 'lax'<\/code>).<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \uc804\uccb4 \ucf54\ub4dc\ub294 \uae43\ud5c8\ube0c\uc5d0\uc11c \ud655\uc778 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<br>You can find the full code on GitHub.<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/gideonslife01\/flm-nextjs\">https:\/\/github.com\/gideonslife01\/flm-nextjs<\/a><\/p>\n\n\n\n<p>\ud83d\udcc1 \uc804\uccb4 \ud504\ub85c\uc81d\ud2b8 \uad6c\uc870 \/ Overall Project Structure<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>myapp project\/  \n\u251c\u2500\u2500 app\/  (Next.js App Router)\n\u2502   \u251c\u2500\u2500 .well-known\/webfinger\/route.ts  -&gt; webfinger\n\u2502   \u251c\u2500\u2500 api\/follow\/route.ts    -&gt; Follow API(temporary)\n\u2502   \u251c\u2500\u2500 api\/announce\/route.ts  -&gt; Boost(Announcement)\n\u2502   \u251c\u2500\u2500 api\/like\/route.ts.     -&gt; Like API\n\u2502   \u251c\u2500\u2500 api\/posts\/route.ts     -&gt; Writing API\n\u2502   \u251c\u2500\u2500 api\/timeline\/route.ts  -&gt; Timeline API\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v1\/instance<\/span>\/route.ts -&gt; auth\n\u2502   \u251c\u2500\u2500 api\/v1\/apps\/route.ts.    -&gt; auth\n\u2502   \u251c\u2500\u2500 api\/v1\/accounts\/verify_credentials\/route.ts -&gt; auth\n\u2502   \u251c\u2500\u2500 api\/v1\/statuses\/route.ts -&gt; Write Post\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v1\/timelines\/home\/route.ts<\/span> -&gt; timeline\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">oauth\/authorize\/route.ts<\/span> -&gt; auth\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">oauth\/token\/route.ts<\/span> -&gt; auth\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v1\/search\/home\/route.ts<\/span> -&gt; search\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v2\/search\/home\/route.ts<\/span> -&gt; search\n\u2502   \u251c\u2500\u2500 api\/v1\/accounts\/&#91;id]\/followers\/route.ts -&gt; followers\n\u2502   \u251c\u2500\u2500 api\/v1\/accounts\/&#91;id]\/following\/route.ts -&gt; following\n\u2502   \u251c\u2500\u2500 <strong>api\/auth\/signup\/route.ts        -&gt; Signup<\/strong>\n\u2502   \u251c\u2500\u2500 <strong>api\/auth\/login\/route.ts         -&gt; Login<\/strong>\n\u2502   \u251c\u2500\u2500<strong> api\/auth\/logout\/route.ts        -&gt; Logout<\/strong>\n\u2502   \u251c\u2500\u2500<strong> api\/auth\/refresh\/route.ts<\/strong> <strong>      -&gt; Refresh Token<\/strong>\n\u2502   \u251c\u2500\u2500<strong> api\/auth\/me\/route.tsx<\/strong> <strong>          -&gt; Login Check\n<\/strong>\u2502   \u251c\u2500\u2500 <strong>auth\/signup\/page.tsx            -&gt; Signup UI<\/strong>\n\u2502   \u251c\u2500\u2500 <strong>auth\/signup\/page.tsx            -&gt; Login UI<\/strong>\n\u2502   \u251c\u2500\u2500 users\/&#91;username]\/\n\u2502   \u2502   \u251c\u2500\u2500 statuses\/&#91;id]\/route.ts -&gt; Indivisual Post\n\u2502   \u2502   \u251c\u2500\u2500 route.ts               -&gt; Acotr Information\n\u2502   \u2502   \u251c\u2500\u2500 followers\/route.ts     -&gt; Followers List\n\u2502   \u2502   \u251c\u2500\u2500 following\/route.ts     -&gt; Following List\n\u2502   \u2502   \u251c\u2500\u2500 inbox\/route.ts         -&gt; Inbox\n\u2502   \u2502   \u2514\u2500\u2500 outbox\/route.ts        -&gt; outbox\n\u2502   \u251c\u2500\u2500 usersui\/&#91;username]\/\n\u2502   \u2502   \u251c\u2500\u2500 page.tsx               -&gt; Timeline UI\n\u2502   \u2502   \u2514\u2500\u2500 _components\/themes\/\n\u2502   \u2502       \u251c\u2500\u2500 themeex\/ThemeexTheme.tsx   -&gt; Example Theme\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/PinaforeTheme.tsx -&gt; Theme 1\n\u2502   \u2502       \u251c\u2500\u2500 mastodon\/MastodonTheme.tsx -&gt; Theme 2\n\u2502   \u2502       \u2514\u2500\u2500 minimal\/MinimaltTheme.tsx  -&gt; Theme 3\n\u2502   \u251c\u2500\u2500 layout.tsx, page.tsx, globals.css\n\u2502   \u2514\u2500\u2500 favicon.ico\n\u251c\u2500\u2500 lib\/\n\u2502   \u251c\u2500\u2500 theme.tsx              -&gt; Theme Provider\n\u2502   \u251c\u2500\u2500 watchThemes.ts         -&gt; Check real-time theme changes\n\u2502   \u251c\u2500\u2500 auth.ts                -&gt; Authentication, User Management\n\u2502   \u251c\u2500\u2500 ap.ts                  -&gt; Follow,Undo,Create,Likes,Announce\n\u2502   \u2514\u2500\u2500 db.ts                  -&gt; DB connection\n\u251c\u2500\u2500 data\/\n\u2502   \u251c\u2500\u2500 keys\/userIDs\/          -&gt; private.pem, public.pem(New)\n\u2502   \u2514\u2500\u2500 keys\/                  -&gt; private.pem, public.pem(legacy)\n\u251c\u2500\u2500 data.sqlite                -&gt; Database(1\/3)\n\u251c\u2500\u2500 data.sqlite-wal            -&gt; Database(2\/3)\n\u251c\u2500\u2500 data.sqlite-shm            -&gt; Database(3\/3)\n\u251c\u2500\u2500 Caddyfile                  -&gt; https \n\u251c\u2500\u2500 instrumentation.ts         -&gt; Background Server\n\u2514\u2500\u2500 package.json<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ud504\ub85c\uc81d\ud2b8 \uc2dc\uc791 \/ Project Start<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-wp-embed is-provider-freelifemakers-org wp-block-embed-freelifemakers-org\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"KQdKDHOD1u\"><a href=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/09\/03\/nextjs-project-summary\/\">[nextjs]\ud504\ub85c\uc81d\ud2b8\uc694\uc57d\/project summary(myapp30+)<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"&#8220;[nextjs]\ud504\ub85c\uc81d\ud2b8\uc694\uc57d\/project summary(myapp30+)&#8221; &#8212; freelifemakers.org\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/09\/03\/nextjs-project-summary\/embed\/#?secret=pQZG6AqfiP#?secret=KQdKDHOD1u\" data-secret=\"KQdKDHOD1u\" width=\"560\" height=\"315\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>\ud83d\udcc1 \ud14c\uc774\ube14 \ucd94\uac00 \/ Add Table<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>  CREATE TABLE IF NOT EXISTS sessions (\n    id TEXT PRIMARY KEY,\n    username TEXT NOT NULL,\n    refresh_token TEXT NOT NULL UNIQUE,\n    access_token TEXT,\n    user_agent TEXT,\n    ip TEXT,\n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n    expires_at DATETIME NOT NULL,\n    last_used_at DATETIME DEFAULT CURRENT_TIMESTAMP\n  );\n\n  CREATE INDEX IF NOT EXISTS idx_sessions_refresh ON sessions(refresh_token);\n  CREATE INDEX IF NOT EXISTS idx_sessions_username ON sessions(username);\n  CREATE INDEX idx_sessions_expires ON sessions(expires_at);<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 session\ud14c\uc774\ube14 \ud544\ub4dc \uc124\uba85 \/ Description of session table fields<\/p>\n\n\n\n<p>&#8212; \uae30\uc874 oauth_tokens\ud14c\uc774\ube14\uc740 pinafore\ub4f1 \uc678\ubd80 \uc571\uc6a9<br>The existing oauth_tokens table is for external apps such as pinafore.<\/p>\n\n\n\n<p>&#8212; session\ud14c\uc774\ube14\uc740 \ub85c\uceec \uc804\uc6a9<br>The session table is local-only<\/p>\n\n\n\n<figure style=\"font-size:14px\" class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\uceec\ub7fc \/ Column<\/th><th>\uac12 \uc608\uc2dc \/ Value Example<\/th><th>\uc124\uba85 \/ Explanation<\/th><\/tr><\/thead><tbody><tr><td><code>id<\/code><\/td><td><code>a1b2c3d4-...<\/code><\/td><td>\uc138\uc158 \uace0\uc720ID<br>Session Unique ID<\/td><\/tr><tr><td><code>username<\/code><\/td><td>user1<\/td><td>\ub85c\uadf8\uc778\ud55c \uc0ac\ub78c<br>Logged-in user<\/td><\/tr><tr><td><code>refresh_token<\/code><\/td><td><code>9f8e7d... (128\uae00\uc790!)<\/code><\/td><td>7\uc77c\uc9dc\ub9ac \ud1a0\ud070- DB\uc5d0\uc11c \uc9c1\uc811 \ud655\uc778\uc6a9<br>7-day token \u2013 for direct verification in the DB<\/td><\/tr><tr><td><code>access_token<\/code><\/td><td><code>eyJ... (JWT)<\/code><\/td><td>15\ubd84\uc9dc\ub9ac \ud1a0\ud070 &#8211; \uc9c0\uae08 \uc0ac\uc6a9\ud558\uace0 \uc788\ub294 \ud1a0\ud070<br>15-minute token \u2013 the token currently in use<\/td><\/tr><tr><td><code>user_agent<\/code><\/td><td><code>Mozilla\/5.0... Chrome<\/code><\/td><td>\ud06c\ub86c\uc5d0\uc11c \ub85c\uadf8\uc778\ud588\ub294\uc9c0 Pinafore!\uc5d0\uc11c!\ud588\ub294\uc9c0 \ud655\uc778\uc6a9<br>To check if you logged in on Chrome or on Pinafore!<\/td><\/tr><tr><td><code>ip<\/code><\/td><td><code>1.2.3.4<\/code><\/td><td>\uc5b4\ub514\uc11c \ub85c\uadf8\uc778 \ud588\ub294\uc9c0  (\ud574\ud0b9 \ud655\uc778\uc6a9)<br>Where the login occurred (to check for hacking)<\/td><\/tr><tr><td><code>expires_at<\/code><\/td><td><code>2026-09-22 10:00:00<\/code><\/td><td>\ub9cc\ub8cc \uc2dc\uac04\uc740 7\uc77c \ud6c4<br>The expiration time is 7 days later<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>\ud83d\udcc1 \ucf54\ub4dc \uc218\uc815 \/ Code Modification<\/p>\n\n\n\n<p>\u2714\ufe0f lib\/db.ts<\/p>\n\n\n\n<p>&#8212; \ub370\uc774\ud130\ubca0\uc774\uc2a4 \ucd08\uae30\ud654 \/ Initialize Database<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>db.exec(`\n... ...\n  -- myapp40 \u2705 Session\n    CREATE TABLE IF NOT EXISTS sessions (\n    id TEXT PRIMARY KEY,\n    username TEXT NOT NULL,\n    refresh_token TEXT NOT NULL UNIQUE,\n    access_token TEXT,\n    user_agent TEXT,\n    ip TEXT,\n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n    expires_at DATETIME NOT NULL,\n    last_used_at DATETIME DEFAULT CURRENT_TIMESTAMP\n  );\n\n`);\n\ndb.exec(`\n... ...\n  -- session -- \u2705 myapp40\n  CREATE INDEX IF NOT EXISTS idx_sessions_refresh ON sessions(refresh_token);\n  CREATE INDEX IF NOT EXISTS idx_sessions_username ON sessions(username);\n  CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);\n`);<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f lib\/auth.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>\/\/ \u2705 myapp40 - 15\ubd84\uc9dc\ub9ac Access Token \/ 15-minute access token\nexport function createAccessToken(username: string) {\n  return jwt.sign({ username, type: 'access' }, JWT_SECRET, { expiresIn: '15m' });\n}\n\n\/\/ \u2705 myapp40 - 7\uc77c\uc9dc\ub9ac Refresh Token (\ub79c\ub364 \ubb38\uc790\uc5f4 + DB \uc800\uc7a5\uc6a9)\n\/\/ 7-day Refresh Token (random string + for DB storage)\nexport function createRefreshToken() {\n  return crypto.randomBytes(64).toString('hex'); \/\/ 128\uae00\uc790 \ub79c\ub364 \/ 128-character random string\n}\n\n\/\/ - \uae30\uc874 \ud638\ud658\uc6a9 (Pinafore OAuth\ub294 \uc544\uc9c1 7\uc77c\uc9dc\ub9ac \uc368\ub3c4 \ub428)\nexport function createToken(username: string) {\n  return jwt.sign({ username }, JWT_SECRET, { expiresIn: '7d' });\n}\n\n... ...\n\n\/\/ \u2705 myapp40 - sessions \ud14c\uc774\ube14\uc5d0 \uc800\uc7a5 \/ Save to the sessions table.\nexport function createSession(username: string, userAgent: string | null, ip: string | null) {\n  const id = crypto.randomUUID();\n  const refreshToken = createRefreshToken();\n  const accessToken = createAccessToken(username);\n\n  db.prepare(`\n    INSERT INTO sessions (id, username, refresh_token, access_token, user_agent, ip, expires_at)\n    VALUES (?,?,?,?,?,?, datetime('now', '+7 days'))\n  `).run(id, username, refreshToken, accessToken, userAgent, ip);\n\n  return { id, refreshToken, accessToken };\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f app\/api\/auth\/login\/route.ts<\/p>\n\n\n\n<p>&#8212; \uc774 \ubd80\ubd84\uc5d0\uc11c \ubcf4\uc548\uc124\uc815 \/ Security settings in this section<br>Security settings in this section<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>    \/\/ \u2705 Access Token - 15\ubd84 - \ubaa8\ub4e0 \uacbd\ub85c\uc5d0\uc11c \uc0ac\uc6a9 \/ Use for all routes\n    res.cookies.set('token', accessToken, {\n      httpOnly: true, \/\/ \u2705 true - JS\uc5d0\uc11c \ubabb \uc77d\uc74c XSS \ubc29\uc5b4 \/ XSS protection: Unreadable by JS\n      secure: IS_PROD, \/\/ only https\n      sameSite: 'lax', \/\/ \ub2e4\ub978 \uc0ac\uc774\ud2b8\uc5d0\uc11c POST\uc694\uccad\uc2dc \ucfe0\ud0a4 \uc548\ubcf4\ub0c4 \/ Cookies are not sent during POST requests from other sites.\n      maxAge: 60*15, \/\/ 15\ubd84 \/ 15minute\n      path: '\/'\n    });\n\n    \/\/ \u2705 Refresh Token - 7\uc77c! - \/api\/auth\/refresh \uc5d0\uc11c\ub9cc \uc0ac\uc6a9\n    res.cookies.set('refresh_token', refreshToken, {\n      httpOnly: true,\n      secure: IS_PROD,\n      sameSite: 'lax',\n      maxAge: 60*60*24*7, \/\/ 7\uc77c \/ 7days\n      path: '\/'\n    });<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f app\/api\/auth\/refresh\/route.ts &#8211; \ub77c\uc6b0\ud2b8 \ucd94\uac00 \/ Add route<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>export async function POST(req: Request) {\n... ...\n  if (!result) {\n    const res = NextResponse.json({ error: 'refresh_token \ub9cc\ub8cc \ub2e4\uc2dc \ub85c\uadf8\uc778 \/ Refresh token expired; please log in again.' }, { status: 401 });\n    res.cookies.set('token', '', { maxAge: 0, path: '\/' });\n    res.cookies.set('refresh_token', '', { maxAge: 0, path: '\/' });\n    return res;\n  }\n... ...\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f app\/api\/auth\/logout\/route.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>export async function POST(req: Request) {\n... ...\n  \/\/ oauth_tokens\ub3c4 \uc0ad\uc81c (Pinafore \ud1a0\ud070)\n  \/\/ Delete oauth_tokens as well (Pinafore tokens)\n  const tokenMatch = cookie.match(\/token=(&#91;^;]+)\/);\n  if (tokenMatch) {\n    try {\n      const db = (await import('@\/lib\/db')).default;\n      db.prepare('DELETE FROM oauth_tokens WHERE access_token =?').run(decodeURIComponent(tokenMatch&#91;1]));\n    } catch {}\n  }\n... ...\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f app\/auth\/login\/page.tsx <\/p>\n\n\n\n<p>&#8212; \ub85c\uadf8\uc778 \uc801\uc6a9 \/ Apply login<\/p>\n\n\n\n<p>&#8212; useEffect\uc5d0\uc11c checkLogin\uc73c\ub85c \ub85c\uadf8\uc778 \ud655\uc778<br>Check login status using <code>checkLogin<\/code> within <code>useEffect<\/code>.<\/p>\n\n\n\n<p>&#8212; \ub85c\uadf8\uc778\ub418\uc5b4 \uc788\uc73c\uba74 currentUser \uc14b\ud305<br>Set currentUser if logged in.<\/p>\n\n\n\n<p>&#8212;  currentUser\ubcc0\uc218 \uac12\uc758 \uc874\uc7ac \uc5ec\ubd80\uc5d0 \ub530\ub77c \ubcf4\uc5ec\uc904 \ud398\uc774\uc9c0 \uc124\uc815<br>Configure the page to be displayed based on the presence of the <code>currentUser<\/code> variable.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:13px\"><code>'use client';\nimport { useState, useEffect, Suspense } from 'react';\nimport { useRouter, useSearchParams } from 'next\/navigation';\n\nfunction LoginForm() {\n  const &#91;loginId, setLoginId] = useState('');\n  const &#91;password, setPassword] = useState('');\n  const &#91;error, setError] = useState('');\n  const &#91;currentUser, setCurrentUser] = useState('');\n  const &#91;loading, setLoading] = useState(true);\n  const router = useRouter();\n  const searchParams = useSearchParams();\n  const next = searchParams.get('next') || '\/';\n\n<strong>  useEffect(() =&gt; {\n    async function checkLogin() {\n      try {\n        const res = await fetch('\/api\/auth\/me', { credentials: 'include' });\n        if (res.ok) {\n          const data = await res.json();\n          if (data.username) {\n            setCurrentUser(data.username);\n          }\n        }\n      } catch {}\n      setLoading(false);\n    }\n    checkLogin();\n  }, &#91;]);<\/strong>\n\n... ... \n\n return (\n    &lt;div style={{ maxWidth: 400, margin: '80px auto', padding: 20 }}&gt;\n      &lt;h1&gt;\ud83d\udd10 \ub85c\uadf8\uc778 \/ Login {currentUser ? `${currentUser} - ` : ''} myapp40&lt;\/h1&gt;\n\n      <strong>{currentUser ? (<\/strong>\n        &lt;&gt;\n        &lt;p style={{ color: 'green', fontSize: 18 }}&gt;\ud604\uc7ac \ub85c\uadf8\uc778: &lt;strong&gt;{currentUser}&lt;\/strong&gt;&lt;\/p&gt;\n\n      <strong>) : (<\/strong>\n\n        &lt;form onSubmit={handleLogin} style={{ display: 'flex', flexDirection: 'column', gap: 12, marginTop: 20 }}&gt; \n\n... ...\n\n       &lt;\/form&gt;\n     <strong> )}<\/strong>\n... ...\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f app\/api\/auth\/me\/route.tsx &#8211; \ub77c\uc6b0\ud2b8 \ucd94\uac00 \/ Add route<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>\/\/ \u2705 myapp40 -  app\/api\/auth\/me\/route.ts\nimport { NextResponse } from 'next\/server';\nimport { verifyToken } from '@\/lib\/auth';\n\nexport async function GET(req: Request) {\n  const cookie = req.headers.get('cookie') || '';\n  const m = cookie.match(\/token=(&#91;^;]+)\/);\n\n  if (!m) {\n    return NextResponse.json({ error: 'Not logged in' }, { status: 401 });\n  }\n\n  const token = decodeURIComponent(m&#91;1]);\n  <strong>const decoded = verifyToken(token);<\/strong>\n\n  if (!decoded?.username) {\n    return NextResponse.json({ error: 'Token expired' }, { status: 401 });\n  }\n\n  return NextResponse.json({ ok: true, username: decoded.username });\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f middleware.ts<\/p>\n\n\n\n<p>&#8212; \ub85c\uadf8\uc778\ud558\uc9c0\uc54a\uc73c\uba74 \ud398\uc774\uc9c0 \ucc28\ub2e8\uae30\ub2a5 \uc0ac\uc6a9\ud560 \uacbd\uc6b0<br>If you use the page blocking function without logging in\u2026<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>export function middleware(req: NextRequest) {\n  const origin = req.headers.get('origin') || '';\n  const path = req.nextUrl.pathname;\n  const DOMAIN = process.env.DOMAIN;\n\n  \/\/ \u2705 \ud5c8\uc6a9\ud560 origin \ubaa9\ub85d!\n  const allowedOrigins = &#91;\n    `https:\/\/${DOMAIN}`,\n    `https:\/\/pinafore.social`,\n    `http:\/\/localhost:3000`,\n    `http:\/\/localhost:3001`\n  ];\n  const isAllowedOrigin = allowedOrigins.includes(origin) ||!origin;\n\n  \/\/ \/\/ ===== 1. \ub85c\uadf8\uc778 \uccb4\ud06c (\ud398\uc774\uc9c0 \ubcf4\ud638) \/ Login check (page protection) =====\n\n  \/\/ const token = req.cookies.get('token')?.value;\n  \/\/ const refreshToken = req.cookies.get('refresh_token')?.value;\n\n  \/\/ \ub85c\uadf8\uc778\uc774 \ud544\uc694\ud55c \ud398\uc774\uc9c0 \/ Page requiring login\n  \/\/ const protectedPaths = &#91;'\/admin', '\/settings', '\/oauth\/authorize'];\n  \/\/ const isProtected = protectedPaths.some(p =&gt; path.startsWith(p));\n\n  \/\/ if (isProtected &amp;&amp;!token &amp;&amp;!refreshToken) {\n  \/\/   \/\/ \ud1a0\ud070 \uc5c6\uc73c\uba74 \ub85c\uadf8\uc778 \ud398\uc774\uc9c0\ub85c \/ Redirect to the login page if there is no token.\n  \/\/   const loginUrl = new URL('\/auth\/login', req.url);\n  \/\/   loginUrl.searchParams.set('next', path);\n  \/\/   return NextResponse.redirect(loginUrl);\n  \/\/ }\n\n... }<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ud14c\uc2a4\ud2b8 \/ Test<\/p>\n\n\n\n<p>\u2714\ufe0f \ucfe0\ud0a4 \uc800\uc7a5 \ubc0f \ubcf4\uc548\uc124\uc815 \uc0c1\ud0dc<br>Cookie storage and security settings status<\/p>\n\n\n\n<p>&#8212; https:\/\/aloy-horizon.duckdns.org\/auth\/login \uc774 \ub77c\uc6b0\ud2b8\uc5d0\uc11c \ub85c\uadf8\uc778\uc774 \uc644\ub8cc\ub418\uba74 \ucfe0\ud0a4\uc640 \ub85c\uceec\uc2a4\ud1a0\ub9ac\uc9c0\uc5d0 \uc800\uc7a5\ub41c \ud1a0\ud070 \ubc0f \uc544\uc774\ub514\ub97c \ud655\uc778 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<br>Once login is completed at the route https:\/\/aloy-horizon.duckdns.org\/auth\/login, you can verify the tokens and IDs stored in cookies and local storage.<\/p>\n\n\n\n<p>&#8212; cmd+option+I(macOS)\ub85c \ud655\uc778 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<br>You can check it using Cmd+Option+I (macOS).<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"562\" height=\"289\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp40-refreshckeck-400-jpg.jpg\" alt=\"\" class=\"wp-image-10306\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp40-refreshckeck-400-jpg.jpg 562w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp40-refreshckeck-400-jpg-300x154.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp40-refreshckeck-400-jpg-400x206.jpg 400w\" sizes=\"auto, (max-width: 562px) 100vw, 562px\" \/><figcaption class=\"wp-element-caption\">Cookie<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"442\" height=\"327\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp40-localstorage-400-jpg.jpg\" alt=\"\" class=\"wp-image-10305\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp40-localstorage-400-jpg.jpg 442w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp40-localstorage-400-jpg-300x222.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp40-localstorage-400-jpg-400x296.jpg 400w\" sizes=\"auto, (max-width: 442px) 100vw, 442px\" \/><figcaption class=\"wp-element-caption\">local storage<\/figcaption><\/figure>\n\n\n\n<p>\u2714\ufe0f \uc11c\ubc84\ub85c\uadf8 \/ Server Log<\/p>\n\n\n\n<p>&#8212; \uc5d1\uc138\uc2a4 \ud1a0\ud070 \ub9cc\ub8cc\uc2dc \uc0c8\ub85c\uc6b4 \uc5d1\uc138\uc2a4 \ud1a0\ud070\uc744 \ubc1c\uae09\ud569\ub2c8\ub2e4.<br>A new access token is issued when the access token expires.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"194\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp40-refreshcheck-serverlog-jpg.jpg\" alt=\"\" class=\"wp-image-10313\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp40-refreshcheck-serverlog-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp40-refreshcheck-serverlog-jpg-300x73.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp40-refreshcheck-serverlog-jpg-768x186.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp40-refreshcheck-serverlog-jpg-400x97.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Server Log<\/figcaption><\/figure>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\uc694\ud55c\ubcf5\uc74c 8\uc7a5 32\uc808 \/ John 8:32<\/p>\n\n\n\n<p>&#8220;\uadf8\ub9ac\uace0 \ub108\ud76c\ub294 \uc9c4\ub9ac\ub97c \uc54c\uac8c \ub420 \uac83\uc774\uba70, \uc9c4\ub9ac\uac00 \ub108\ud76c\ub97c \uc790\uc720\ub86d\uac8c \ud560 \uac83\uc774\ub2e4.&#8221;<\/p>\n\n\n\n<p>&#8220;Then you will know the truth ,and the truth will set you free&#8221;<\/p>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\udc49\ud83c\udffb \ud68c\uc6d0\uac00\uc785 \ubc0f \ub85c\uadf8\uc778\uacfc \uad00\ub828\ub41c \uae30\ub2a5\uc785\ub2c8\ub2e4.These are features related to sign-up and login. \ud83d\udc49\ud83c\udffb myapp40\uc5d0\uc11c\ub294 \uae30\uc874 \ub85c\uadf8\uc778 \ubcf4\uc548\uc744 \uc5c5\uadf8\ub808\uc774\ub4dc\ud569\ub2c8\ub2e4.myapp40 is upgrading its existing login security. \ud83d\udc49\ud83c\udffb \uae30\uc874 access token\uc678\uc5d0 refresh token\uc744 \ucd94\uac00\ud558\uace0 session \ud14c\uc774\ube14\uc5d0 \uc800\uc7a5\ud569\ub2c8\ub2e4.In addition to the existing access token, a refresh token is added and stored in the session table. \ud83d\udc49\ud83c\udffb access token\uc740 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":10306,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[28,27,1,7],"tags":[],"class_list":["post-10239","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-news","category-nextjs","category-uncategorized","category-website","has-thumbnail"],"_links":{"self":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/10239","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/comments?post=10239"}],"version-history":[{"count":72,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/10239\/revisions"}],"predecessor-version":[{"id":10322,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/10239\/revisions\/10322"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media\/10306"}],"wp:attachment":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=10239"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=10239"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=10239"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}