{"id":12906,"date":"2026-10-26T09:37:40","date_gmt":"2026-10-26T00:37:40","guid":{"rendered":"https:\/\/www.freelifemakers.org\/wordpress\/?p=12906"},"modified":"2026-10-26T18:18:41","modified_gmt":"2026-10-26T09:18:41","slug":"nextjs-sns-server-51-9","status":"publish","type":"post","link":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/10\/26\/nextjs-sns-server-51-9\/","title":{"rendered":"[nextjs]SNS Server-51(myapp61+)[\uc678\ubd80\uc571\uc5d0\uc11c\ub85c\uadf8\uc778\/Log in with an external app]\u00a0"},"content":{"rendered":"\n<p>\ud83d\udc49\ud83c\udffb \uc774\ubc88\uc5d0\ub294 \uc678\ubd80\uc571 \ub85c\uadf8\uc778 \ubd80\ubd84\uc5d0 \ub300\ud574\uc11c \uc0b4\ud3b4\ubd05\ub2c8\ub2e4.<br>Next, we will look at the section regarding logging in via external apps.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \ud68c\uc6d0\uac00\uc785\uc740 \ub85c\uceec\uc5d0\uc11c\ub9cc \uac00\ub2a5\ud558\uace0 \uc678\ubd80\uc571 \ub85c\uadf8\uc778\uc744 \uc704\ud574\uc11c \/oauth\/authorize \ub77c\uc6b0\ud2b8\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4.<br>User registration is only possible locally, and the <code>\/oauth\/authorize<\/code> route is used for logging in via external applications.<\/p>\n\n\n\n<p>\ud83d\udcc1 \uc678\ubd80 \uc571 \ubc0f \ub85c\uceec UI\ubd80\ubd84 \uc778\uc99d \ubd80\ubd84 \uad6c\uc870<br>Authentication-related structure for external apps and the local UI<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>app\/api\/auth\/\n  \u251c\u2500\u2500 signup\/route.ts      -&gt; \uc6f9\uc0ac\uc774\ud2b8 \ud68c\uc6d0\uac00\uc785 \/ website signup\n  \u251c\u2500\u2500 login\/route.ts       -&gt; \uc6f9\uc0ac\uc774\ud2b8 \ub85c\uadf8\uc778 \/ website login\n  \u251c\u2500\u2500 logout\/route.ts      -&gt; \ub85c\uadf8\uc544\uc6c3 \/ logout\n  \u251c\u2500\u2500 refresh\/route.ts     -&gt; refresh_token\uc73c\ub85c access token\uc7ac\ubc1c\uae09\n  |                        -&gt; Reissuing an access token using a refresh token\n  \u2514\u2500\u2500 me\/route.ts          -&gt; \ub85c\uadf8\uc778 \uccb4\ud06c \/ Login check\n\napp\/auth\/\n  \u251c\u2500\u2500 signup\/page.tsx      -&gt; \ud68c\uc6d0\uac00\uc785 UI \/ Sign-up UI\n  \u2514\u2500\u2500 login\/page.tsx       -&gt; \ub85c\uadf8\uc778 UI \/ Login UI\n\noauth\/authorize\/route.ts   -&gt; OAuth \ub3d9\uc758 \ud654\uba74 \/ OAuth consent screen\noauth\/token\/route.ts       -&gt; access_token \ubc1c\uae09 \/ Issuance of an access token\n\nlib\/auth.ts                -&gt; \uc2e4\uc81c \uc720\uc800 \uac80\uc99d \ub85c\uc9c1 (getUser, \uc138\uc158 \ud655\uc778)\n                           -&gt; Actual user verification logic (getUser, session check)<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \uc678\ubd80 \uc571 \ub85c\uadf8\uc778(pinafore)<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>https:\/\/aloy-horizon.duckdns.org\/oauth\/authorize?client_id=5dc45054-beb... ... &amp;redirect_uri=https%3A%2F%2Fpinafore.social%2Fsetting... ...<\/code><\/pre>\n\n\n\n<p>1)pinafore.social\uc5d0\uc11c \ub85c\uadf8\uc778\uc2dc \uc704\uc640 \uac19\uc740 url\ub85c \/oauth\/authorize \ub77c\uc6b0\ud2b8\uc5d0 \uc694\uccad\ud569\ub2c8\ub2e4.<br>When logging in via pinafore.social, a request is made to the <code>\/oauth\/authorize<\/code> route using the URL shown above.<\/p>\n\n\n\n<p>2)\ud68c\uc6d0\uac00\uc785\uc740 \ub85c\uceec \uc6f9\uc0ac\uc774\ud2b8\uc5d0\uc11c\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.<br>Registration is only possible on the local website.<\/p>\n\n\n\n<p>\ud83d\udcc1 \/oauth\/authorize\/route.ts &#8211; \ub85c\uadf8\uc778 \ud3fc \/ Login Form<\/p>\n\n\n\n<p>\u2714\ufe0f \ub85c\uadf8\uc778\ud558\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \/ If not logged in<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>  if (!username) {\n    const html = `\n    &lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;&lt;meta charset=\"utf-8\"&gt;&lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"&gt;\n    &lt;title&gt;Login&lt;\/title&gt;\n    &lt;style&gt;body{font-family:sans-serif;max-width:400px;margin:50px auto;padding:20px}input{width:90%;padding:12px;margin:8px 0;font-size:16px}button{width:100%;padding:12px;background:#6364ff;color:white;border:0;border-radius:8px;font-size:16px}&lt;\/style&gt;\n    &lt;\/head&gt;&lt;body&gt;\n      &lt;h2&gt;\ud83d\udd10 \ub85c\uadf8\uc778\/ Login(OAuth)&lt;\/h2&gt;\n      &lt;p&gt;${clientId} \uc571 \uc2b9\uc778 \uc704\ud574 \ub85c\uadf8\uc778 \/ Login to authorize ${clientId}&lt;\/p&gt;\n      &lt;form method=\"POST\" action=\"<strong>\/oauth\/login-and-authorize<\/strong>\"&gt;\n        &lt;input type=\"hidden\" name=\"client_id\" value=\"${clientId}\"&gt;\n        &lt;input type=\"hidden\" name=\"redirect_uri\" value=\"${redirectUri}\"&gt;\n        &lt;input type=\"hidden\" name=\"scope\" value=\"${scope}\"&gt;\n        &lt;input name=\"username\" placeholder=\"username\" required&gt;\n        &lt;input name=\"password\" type=\"password\" placeholder=\"password\" required&gt;\n        &lt;button type=\"submit\"&gt;\ub85c\uadf8\uc778 &amp; \uc2b9\uc778 \/ Login &amp; Authorize&lt;\/button&gt;\n      &lt;\/form&gt;\n    &lt;\/body&gt;&lt;\/html&gt;`;\n    return new NextResponse(html, { headers: { 'Content-Type': 'text\/html; charset=utf-8' } });\n  }<\/code><\/pre>\n\n\n\n<p>1) \ub85c\uadf8\uc778 \ud3fc\uc744 \ubcf4\uc5ec\uc8fc\ub294 \ubd80\ubd84\uc785\ub2c8\ub2e4.  \/ This is the section that displays the login form.<\/p>\n\n\n\n<p>2)fetch\ub85c \ub85c\uadf8\uc778 \uc2dc\ub3c4\uc2dc \uc758\ub3c4\ub300\ub85c \ub3d9\uc791\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0\uac00 \uc788\uc5b4\uc11c HTML\uc758 form action\uc744 \uc0ac\uc6a9\ud569\ub2c8\ub2e4.<br>Since login attempts via &#8216;etch&#8217; may not always function as intended, we use an HTML form action instead.<\/p>\n\n\n\n<p>3) \/oauth\/login-and-authorize \ub77c\uc6b0\ud2b8\ub85c \ud3fc \ub370\uc774\ud130\ub97c \uc804\uc1a1\ud569\ub2c8\ub2e4.<br>Submit the form data to the \/oauth\/login-and-authorize route.<\/p>\n\n\n\n<p>\u2714\ufe0f \ub85c\uadf8\uc778\ud55c \uacbd\uc6b0 \/ If logged in<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>  let clientName = 'Unknown App';\n  try {\n    const app = db.prepare('SELECT client_name FROM oauth_apps WHERE client_id =?').get(clientId) as any;\n    if (app) clientName = app.client_name;\n  } catch {}\n\n  const html = `\n  &lt;!DOCTYPE html&gt;\n  &lt;html&gt;\n  &lt;head&gt;\n    &lt;meta charset=\"utf-8\"&gt;\n    &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"&gt;\n    &lt;title&gt;Authorize ${clientName}&lt;\/title&gt;\n    &lt;style&gt;\n      body { font-family: sans-serif; max-width: 500px; margin: 50px auto; padding: 20px; }\n     .card { border: 1px solid #ccc; border-radius: 10px; padding: 20px; }\n      button { background: #6364ff; color: white; border: none; padding: 12px 20px; border-radius: 8px; font-size: 16px; cursor: pointer; width: 100%; }\n      button:hover { background: #5354ee; }\n     .info { background: #f5f5ff; padding: 10px; border-radius: 5px; margin: 15px 0; }\n    &lt;\/style&gt;\n  &lt;\/head&gt;\n  &lt;body&gt;\n    &lt;div class=\"card\"&gt;\n      &lt;h2&gt;\ud83d\udd10 ${clientName} \uc2b9\uc778&lt;\/h2&gt;\n      &lt;div class=\"info\"&gt;\n        &lt;strong&gt;${clientName}&lt;\/strong&gt;\uc774 \ub2e4\uc74c \uad8c\ud55c\uc744 \uc694\uccad\ud569\ub2c8\ub2e4:&lt;br&gt;\n        &lt;code&gt;${scope}&lt;\/code&gt;\n      &lt;\/div&gt;\n      &lt;p&gt;\uacc4\uc815: &lt;strong&gt;${username}@aloy-horizon.duckdns.org&lt;\/strong&gt;&lt;\/p&gt;\n      &lt;form method=\"POST\" action=\"\/<strong>oauth\/authorize<\/strong>\"&gt;\n        &lt;input type=\"hidden\" name=\"client_id\" value=\"${clientId}\"&gt;\n        &lt;input type=\"hidden\" name=\"redirect_uri\" value=\"${redirectUri}\"&gt;\n        &lt;input type=\"hidden\" name=\"scope\" value=\"${scope}\"&gt;\n        &lt;button type=\"submit\"&gt;\u2705 \uc2b9\uc778\ud558\uae30\/Approval&lt;\/button&gt;\n      &lt;\/form&gt;\n      &lt;p style=\"font-size:12px; color:#888; margin-top:15px;\"&gt;myapp39 - ${username}\ub85c \ub85c\uadf8\uc778\ub428&lt;\/p&gt;\n    &lt;\/div&gt;\n  &lt;\/body&gt;\n  &lt;\/html&gt;\n  `;\n\n  return new NextResponse(html, {\n    headers: { 'Content-Type': 'text\/html; charset=utf-8' }\n  });<\/code><\/pre>\n\n\n\n<p>1)\uc774\ubbf8 \ub85c\uadf8\uc778\ub418\uc5b4 \uc788\ub294 \uacbd\uc6b0 \uc571 \uc2b9\uc778 \uc694\uccad\ud654\uba74\uc744 \ubcf4\uc5ec\uc90d\ub2c8\ub2e4.<br>If the user is already logged in, the app authorization request screen is displayed.<\/p>\n\n\n\n<p>2)\uc2b9\uc778\ud558\uae30 \ubc84\ud2bc(submit)\uc744 \ub204\ub974\uba74 \/oauth\/authorize \ub77c\uc6b0\ud2b8\ub85c \ud3fc \ub370\uc774\ud130\ub97c \uc804\uc1a1\ud569\ub2c8\ub2e4.<br>Clicking the &#8220;submit&#8221; button sends the form data to the \/oauth\/authorize route.<\/p>\n\n\n\n<p>\ud83d\udcc1 \/oauth\/authorize\/route.ts &#8211; \uc571 \uc2b9\uc778 \ucc98\ub9ac \/ App Approval Process<\/p>\n\n\n\n<p>\u2714\ufe0f randomUUID\ub97c \ub9cc\ub4e4\uc5b4\uc11c  oauth_codes\ud14c\uc774\ube14\uc5d0 \uc800\uc7a5\ud569\ub2c8\ub2e4.<br>Generate a random UUID and store it in the <code>oauth_codes<\/code> table.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:13px\"><code>... ...\n  const code = randomUUID();\n\n  try {\n    db.prepare(`\n      CREATE TABLE IF NOT EXISTS oauth_codes (\n        code TEXT PRIMARY KEY,\n        client_id TEXT,\n        redirect_uri TEXT,\n        scope TEXT,\n        username TEXT,\n        created_at INTEGER\n      )\n    `).run();\n\n    db.prepare(`\n      INSERT INTO oauth_codes (code, client_id, redirect_uri, scope, username, created_at)\n      VALUES (?,?,?,?,?,?)\n    `).run(code, clientId, redirectUri, scope, username, Date.now());\n... ...<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \/oauth\/authorize\/route.ts &#8211; \uc571\uc73c\ub85c \ub2e4\uc2dc \ub3cc\uc544\uac00\uae30 \/ Return to the app<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>1.\ub9ac\ub2e4\uc774\ub809\ud2b8 URI\uac00 \uc5c6\ub294 \uacbd\uc6b0\nWhen there is no redirect URI\n\nif (!redirectUri || redirectUri === 'urn:ietf:wg:oauth:2.0:oob' || redirectUri.includes('oob')) { ... }\n\n2.Tursky\uc571\uc778 \uacbd\uc6b0 \/ In the case of the Tursky app\n\nif (redirectUri.startsWith('oauth2redirect:\/\/') || redirectUri.includes('tusky') ||         redirectUri.includes('com.keylesspalace')) { ... }\n\n3.PC\uc6f9 \ube0c\ub77c\uc6b0\uc800\uc778 \uacbd\uc6b0 \/ For PC web browsers\n\n const redirectUrl = new URL(redirectUri);\n redirectUrl.searchParams.set('code', code);\n return NextResponse.redirect(redirectUrl.toString());<\/code><\/pre>\n\n\n\n<p>1)\uc571\uc5d0\uc11c \ub85c\uadf8\uc778\uc2dc\ub3c4\ud558\ub294\uac74\uc9c0 PC \uc6f9\ube0c\ub77c\uc6b0\uc800\uc5d0\uc11c \ub85c\uadf8\uc778 \uc2dc\ub3c4\ud558\ub294\uac74\uc9c0\uc5d0 \ub530\ub77c\uc11c \ub9ac\ub2e4\uc774\ub809\ud2b8 \uc2e4\ud589<br>Execute a redirect based on whether the login attempt is made from an app or a PC web browser.<\/p>\n\n\n\n<p>2)\ub9ac\ub2e4\uc774\ub809\ud2b8 \uc2e4\ud589 \uc911 \/oauth\/token \ub77c\uc6b0\ud2b8 \uc2e4\ud589(\uc678\ubd80 \uc571\uc5d0\uc11c \uc2e4\ud589\ud568)<br>Executing redirect; invoking the \/oauth\/token route (triggered by an external app)<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>... ...\n    \/\/ \u2705 access_token \ubc1c\uae09 \/ Issue access_token\n    const accessToken = randomUUID() + randomUUID();\n... ...\n      db.prepare(`\n        INSERT INTO oauth_tokens (access_token, client_id, username, scope, created_at)\n        VALUES (?,?,?,?,?)\n      `).run(accessToken, clientId, oauthCode.username, oauthCode.scope, Date.now());\n\n      \/\/ \u2705 code\ub294 \ud55c\ubc88 \uc4f0\uba74 \uc0ad\uc81c (\uc7ac\uc0ac\uc6a9 \ubc29\uc9c0) \/ The code should be deleted after one use! (Prevent reuse!)\n      db.prepare('DELETE FROM oauth_codes WHERE code = ?').run(code);\n... ...<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>\ud83d\udca1 \n\/oauth\/authorize \ub610\ub294 \/oauth\/logiin-and-authorize\uc5d0\uc11c oauth_codes\ud14c\uc774\ube14\uc5d0 code,cliend_id\ub97c \uc785\ub825\ud568\n\/oauth\/token\ub77c\uc6b0\ud2b8\ub97c \uc678\ubd80\uc571\uc5d0\uc11c \uc2e4\ud589\ud574\uc11c access token\uc744 \ubc1c\uae09\ud558\uace0 oauth_codes\uc5d0\uc11c cliend_id\ub97c \uc0ad\uc81c\ud568.\n\uadf8\ub798\uc11c \/oauth\/authorize \ub610\ub294 \/oauth\/logiin-and-authorize\uc5d0\uc11c \ub370\uc774\ud130\ub97c oauth_codes\ud14c\uc774\ube14\uc5d0 \uc785\ub825\ud558\uc9c0\ub9cc\n\uc2e4\uc81c\ub85c \ub370\uc774\ud130\uac00 \ub298\uc5b4\ub098\uc9c0\ub294 \uc54a\uc74c\n\n\uc678\ubd80 \uc571(pinafore)\uc5d0\uc11c \ub85c\uadf8\uc778\uc2dc \/oauth\/token\uc744 \uc2e4\ud589\ud574\uc11c oauth_codes\uc758 \uac12\uc744 \uc9c0\uc6c0\n\uadf8\ub798\uc11c oauth_codes\ub294 \ud56d\uc0c1 0\uac1c \uc720\uc9c0\uac00 \uc815\uc0c1<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>\u2714\ufe0f \/oauth\/login-and-authorize\/route.ts <\/p>\n\n\n\n<p>&#8211; \ub85c\uadf8\uc778\uc2dc \uc571\uc2b9\uc778\ucc98\ub9ac\uc640 \ucfe0\ud0a4 \uc800\uc7a5\uc744 \ub3d9\uc2dc \ucc98\ub9ac\ud568.(\ub85c\uadf8\uc778\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc740 \uacbd\uc6b0)<br>Handles app authorization and cookie storage simultaneously upon login (if not already logged in).<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>... ...\n\n \/\/ \uc138\uc158 \ud14c\uc774\ube14\uc5d0 \ud1a0\ud070\uc800\uc7a5 \/ Storing tokens in session tables\n  \/\/ oauth\ub300\uc2e0\uc5d0 ip\uc8fc\uc18c \uc785\ub825 \uac00\ub2a5\ud568,\uc77c\ubc18\ub85c\uadf8\uc778\uc740 ip \uc678\ubd80\uc571 \ub85c\uadf8\uc778\uc740 oauth\ub85c \uad6c\ubd84\ud574\uc11c \uc800\uc7a5\ud558\uae30 \uc704\ud568\n  \/\/ You can enter an IP address instead of using OAuth; \n  \/\/ this allows for distinguishing between standard logins (via IP) and external app logins (via OAuth) when storing the data.\n  \nconst { refreshToken, accessToken } = createSession(user.username, req.headers.get('user-agent'), 'oauth');\n... ...\n\n\n\/\/ \uc571\uc2b9\uc778,\ub370\uc774\ud130\ubca0\uc774\uc2a4\uc5d0 \uc800\uc7a5\ud558\uae30\n\/\/ App approval, saving to the database\n\n  const insertResult = db.prepare(`INSERT INTO oauth_codes VALUES (?,?,?,?,?,?)`).run(code, clientId, redirectUri, scope, user.username, Date.now());\n  \/\/ console.log('INSERT result:', insertResult.changes, insertResult.lastInsertRowid);\n... ...\n\n\/\/ \ucfe0\ud0a4 \uc800\uc7a5 \/ Save cookies\n\n  res.cookies.set('token', accessToken, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 60*15, path: '\/' });\n  res.cookies.set('refresh_token', refreshToken, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 60*60*24*7, path: '\/' });\n... ...<\/code><\/pre>\n\n\n\n<p>-Tursky\uc571\uc778 \uacbd\uc6b0\uc640 \uc77c\ubc18 PC\uc571\uc778 \uacbd\uc6b0 \ub9ac\ub2e4\uc774\ub809\ud2b8 \uc124\uc815<br>Redirect settings for Tursky apps versus standard PC apps<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>... ...\n  if (finalUrl.startsWith('oauth2redirect:\/\/') || finalUrl.includes('tusky') || finalUrl.includes('com.keylesspalace')) {\n    html = `&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;&lt;meta charset=\"utf-8\"&gt;&lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"&gt;&lt;\/head&gt;\n    &lt;body style=\"font-family:sans-serif;max-width:500px;margin:50px auto;padding:20px;text-align:center\"&gt;\n      &lt;h3&gt;\u2705 ${user.username} \uc2b9\uc778\ub428 \/ Authorized&lt;\/h3&gt;\n      &lt;p&gt;Tusky\ub85c \ub3cc\uc544\uac00\ub294 \uc911 \/ Transferring to Tusky...&lt;\/p&gt;\n      &lt;p&gt;&lt;a href=\"${finalUrl}\" style=\"word-break:break-all\"&gt;${finalUrl}&lt;\/a&gt;&lt;\/p&gt;\n      &lt;script&gt;setTimeout(()=&gt;{window.location.href=\"${finalUrl}\"},300)&lt;\/script&gt;\n    &lt;\/body&gt;&lt;\/html&gt;`;\n  } else {\n    try {\n      const u = new URL(redirectUri);\n      u.searchParams.set('code', code);\n      html = `&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;body&gt;&lt;script&gt;window.location.href=\"${u.toString()}\"&lt;\/script&gt;&lt;a href=\"${u.toString()}\"&gt;Continue&lt;\/a&gt;&lt;\/body&gt;&lt;\/html&gt;`;\n    } catch {\n      html = `&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;body&gt;&lt;script&gt;window.location.href=\"${finalUrl}\"&lt;\/script&gt;&lt;a href=\"${finalUrl}\"&gt;${finalUrl}&lt;\/a&gt;&lt;\/body&gt;&lt;\/html&gt;`;\n    }\n  }\n... ...<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \uacb0\uacfc \/ Result<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>1.\ub85c\uadf8\uc778\uc774 \ub418\uc5b4 \uc788\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \/ If you are not logged in\n- \/oauth\/authorize\/route.ts \ub77c\uc6b0\ud2b8 \uc2e4\ud589(GET)\nExecution of the \/oauth\/authorize\/route.ts route(GET)\n\n- \/oauth\/login-and-authorize \ub77c\uc6b0\ud2b8\uc5d0\uc11c \uc571\uc2b9\uc778 \ubc0f \ub85c\uadf8\uc778\ucc98\ub9ac(POST)\nHandle app authorization and login in the \/oauth\/login-and-authorize route(POST)\n\n2.\ub85c\uadf8\uc778 \ub418\uc5b4 \uc788\ub294 \uacbd\uc6b0 \/ If logged in\n- \/oauth\/authorize\/route.ts \ub77c\uc6b0\ud2b8 \uc2e4\ud589(GET)\nExecution of the \/oauth\/authorize\/route.ts route (GET)\n\n- \/oauth\/authorize\/route.ts \ub77c\uc6b0\ud2b8\uc5d0\uc11c \uc571\uc2b9\uc778 \ubc0f \ub85c\uadf8\uc778\ucc98\ub9ac(POST)\nHandling app authorization and login in the \/oauth\/authorize\/route.ts route<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ud14c\uc2a4\ud2b8 \/ Test<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"500\" height=\"741\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/merge-jpg.jpg\" alt=\"\" class=\"wp-image-12981\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/merge-jpg.jpg 500w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/merge-jpg-202x300.jpg 202w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/merge-jpg-400x593.jpg 400w\" sizes=\"auto, (max-width: 500px) 100vw, 500px\" \/><\/figure><\/div>\n\n\n<p><\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\ub9c8\ud0dc\ubcf5\uc74c 21\uc7a5 22\uc808 \/ Matthew 21: 22<\/p>\n\n\n\n<p>&#8220;\ub108\ud76c\uac00 \uae30\ub3c4\ud560 \ub54c\uc5d0 \ubb34\uc5c7\uc774\ub4e0\uc9c0 \ubbff\uace0 \uad6c\ud558\ub294 \uac83\uc740 \ub2e4 \ubc1b\uc73c\ub9ac\ub77c \ud558\uc2dc\ub2c8\ub77c&#8221;<\/p>\n\n\n\n<p>&#8220;And everything you ask in prayer, believing, you will receive.&#8221;<\/p>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\udc49\ud83c\udffb \uc774\ubc88\uc5d0\ub294 \uc678\ubd80\uc571 \ub85c\uadf8\uc778 \ubd80\ubd84\uc5d0 \ub300\ud574\uc11c \uc0b4\ud3b4\ubd05\ub2c8\ub2e4.Next, we will look at the section regarding logging in via external apps. \ud83d\udc49\ud83c\udffb \ud68c\uc6d0\uac00\uc785\uc740 \ub85c\uceec\uc5d0\uc11c\ub9cc \uac00\ub2a5\ud558\uace0 \uc678\ubd80\uc571 \ub85c\uadf8\uc778\uc744 \uc704\ud574\uc11c \/oauth\/authorize \ub77c\uc6b0\ud2b8\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4.User registration is only possible locally, and the \/oauth\/authorize route is used for logging in via external applications. \ud83d\udcc1 \uc678\ubd80 \uc571 \ubc0f \ub85c\uceec UI\ubd80\ubd84 \uc778\uc99d \ubd80\ubd84 \uad6c\uc870Authentication-related [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":12981,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[28,27,1,7],"tags":[],"class_list":["post-12906","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\/12906","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=12906"}],"version-history":[{"count":76,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/12906\/revisions"}],"predecessor-version":[{"id":12985,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/12906\/revisions\/12985"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media\/12981"}],"wp:attachment":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=12906"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=12906"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=12906"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}