Module 7: LINE Login

OAuth 2.0 + OpenID Connect identity provider — let users log into your web app with their LINE account.

⏱ 25 min read 📖 Intermediate

7.1 What is LINE Login?

LINE Login allows users to sign into your website or app using their LINE account. It's based on OAuth 2.0 with OpenID Connect — a standard, secure authentication protocol.

Key benefits:

  • No password to manage — users authenticate with LINE
  • Get their LINE User ID, display name, profile picture
  • Link the user's identity across your web app and LINE OA
  • High trust in Thailand — users already have LINE
Important: LINE Login uses a different channel type than Messaging API. You create a "LINE Login" channel in Developers Console. But both channels can be under the same Provider.

7.2 LINE Login vs LIFF

AspectLINE LoginLIFF
Channel typeLINE Login channelMessaging API channel
Where it worksAny browser (not just LINE)Inside LINE app (webview)
Login flowOAuth redirect + callbackAutomatic via liff.init()
Use caseYour website outside LINEWeb app inside LINE
Bot messagingNoYes (sendMessages)

Common pattern: Use LIFF for "inside LINE" experiences and LINE Login for "outside LINE" web access. They complement each other.

7.3 OAuth 2.0 Flow

LINE Login uses the Authorization Code Grant flow:

  1. User clicks "Log in with LINE" on your site
  2. Redirected to LINE's authorization page
  3. User consents and authorizes
  4. LINE redirects back to your callback URL with an authorization code
  5. Your server exchanges the code for an access token and ID token
  6. Use the ID token to identify the user
// Step 1: Redirect user to LINE
https://access.line.me/oauth2/v2.1/authorize?
  response_type=code&
  client_id={CHANNEL_ID}&
  redirect_uri=https://yourdomain.com/callback&
  state=random-state-string&
  scope=profile%20openid%20email
// Step 2: Exchange code for token (server-side)
POST https://api.line.me/oauth2/v2.1/token

Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
code={AUTHORIZATION_CODE}
redirect_uri=https://yourdomain.com/callback
client_id={CHANNEL_ID}
client_secret={CHANNEL_SECRET}

Response:
{
  "access_token": "...",
  "id_token": "jwt...",
  "refresh_token": "...",
  "expires_in": 2592000,  // 30 days
  "token_type": "Bearer"
}
// Step 3: Verify ID token (server-side)
// The ID token is a JWT signed by LINE
POST https://api.line.me/oauth2/v2.1/verify

id_token={ID_TOKEN}
client_id={CHANNEL_ID}

Response:
{
  "iss": "https://access.line.me",
  "sub": "U4a8b...",  // LINE User ID
  "aud": "165...",
  "exp": 1690000000,
  "iat": 1689996400,
  "name": "John Doe",
  "picture": "https://profile.line-scdn.net/...",
  "email": "john@example.com"  // only if email scope
}

7.4 Account Linking — Connect LINE User to Your System

Account Linking securely connects a LINE user with a user account in your system. This is how you identify who the user is across channels.

The Linking Flow:

  1. You issue a link token via Messaging API
  2. Send it to the user in a chat message as a URL
  3. User taps the URL (opens in browser)
  4. They log into your system and confirm linking
  5. Your server receives a accountLink webhook event

Step 1: Issue link token

POST https://api.line.me/v2/bot/user/{userId}/linkToken

Response: { "linkToken": "G7x3K..." }

Step 2: Send link URL to user

const linkToken = 'G7x3K...';  // from API above
const linkUrl = `https://yourdomain.com/link?linkToken=${linkToken}`;

// Send as rich menu action or message
await replyMessage(replyToken, {
  type: 'text',
  text: 'Please link your account:',
  quickReply: {
    items: [{
      type: 'action',
      action: {
        type: 'uri',
        label: 'Link Account',
        uri: linkUrl
      }
    }]
  }
});

Step 3: Handle the webhook event

// When linking is complete, LINE sends:
{
  "type": "accountLink",
  "replyToken": "...",
  "source": { "userId": "U4a8b..." },
  "link": {
    "result": "ok",        // or "failed"
    "nonce": "random-nonce" // matches your nonce
  }
}

// Now you know: LINE User ID U4a8b... is linked to user account #123
After linking, you can push targeted messages based on the user's account status (e.g., order updates, membership benefits) using the LINE User ID.

7.5 LINE Login Buttons

LINE provides official login button assets that you should use:

  • Green button: "Log in with LINE" (standard)
  • Use the official assets from LINE Login Design Guidelines
  • Button must be clearly visible and follow LINE brand guidelines

7.6 Practical Example: Node.js LINE Login

const express = require('express');
const axios = require('axios');
const app = express();

const CLIENT_ID = process.env.LINE_LOGIN_CHANNEL_ID;
const CLIENT_SECRET = process.env.LINE_LOGIN_CHANNEL_SECRET;
const REDIRECT_URI = 'https://yourdomain.com/auth/callback';

// Step 1: Redirect to LINE
app.get('/auth/login', (req, res) => {
  const state = require('crypto').randomBytes(16).toString('hex');
  const url = 'https://access.line.me/oauth2/v2.1/authorize?' +
    `response_type=code&client_id=${CLIENT_ID}` +
    `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` +
    `&state=${state}&scope=profile%20openid%20email`;
  res.redirect(url);
});

// Step 2: Handle callback
app.get('/auth/callback', async (req, res) => {
  const { code, state } = req.query;

  // Exchange code for tokens
  const tokenRes = await axios.post(
    'https://api.line.me/oauth2/v2.1/token',
    new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      redirect_uri: REDIRECT_URI,
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET
    }),
    { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
  );

  const { id_token, access_token } = tokenRes.data;

  // Verify ID token
  const verifyRes = await axios.post(
    'https://api.line.me/oauth2/v2.1/verify',
    new URLSearchParams({ id_token, client_id: CLIENT_ID }),
    { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
  );

  const profile = verifyRes.data;
  // profile.sub = LINE User ID
  // profile.name = display name
  // profile.picture = profile picture URL

  // Find or create user in your DB
  // let user = await db.findUserByLineId(profile.sub);
  // if (!user) user = await db.createUser({ lineId: profile.sub, name: profile.name });

  // Set session / JWT
  res.json({ user: profile });
});