How LINEJS Authentication and Token Refresh Work: A Complete Technical Guide

LINEJS handles authentication by storing a short-lived authToken and long-lived refreshToken, automatically detecting expiration via MUST_REFRESH_V3_TOKEN errors and seamlessly refreshing tokens without user intervention.

The LINEJS library (evex-dev/linejs) provides a robust authentication system for the LINE messaging API. Understanding how LINEJS manages authentication and token refresh is essential for building reliable bots and clients that maintain persistent connections without manual re-authentication.

Understanding the LINEJS Authentication Model

LINEJS implements a dual-token architecture that separates short-term access from long-term session persistence.

Token Storage Architecture

The client maintains two critical pieces of authentication data:

  • authToken – Stored in BaseClient.authToken and exposed via Client.authToken. This short-lived LINE access token is sent as the x-line-access header in every HTTP request.
  • refreshToken – Persisted in the client's storage layer under the key "refreshToken". This long-lived token enables the acquisition of new authToken values when the current one expires.

Both tokens are emitted through the update:authtoken event, allowing applications to react to authentication state changes.

Event-Driven Token Updates

The authentication system uses an event-based pattern defined in packages/linejs/base/core/utils/events.ts. When tokens change, the client emits events that consuming applications can listen to for persistence or UI updates.

Initial Login and Token Acquisition

The login logic resides in packages/linejs/base/login/mod.ts, which handles multiple authentication methods.

Password-Based Authentication

The withPassword method executes the following sequence after successful credential validation:

  1. Calls client.auth.refresh to obtain initial tokens
  2. Extracts the accessToken and refreshToken from the response
  3. Stores the authToken in memory and emits the update:authtoken event
  4. Persists the refreshToken to storage for future use
// Simplified flow from login/mod.ts (lines 140-155)
this.client.authToken = tokenInfo[1];               // access token
this.client.emit("update:authtoken", tokenInfo[1]);
await this.client.storage.set("refreshToken", tokenInfo[2]); // refresh token

QR Code and Direct Token Login

QR-code login follows an identical token storage path once the QR flow completes. Direct auth-token login bypasses server contact and immediately emits the supplied token via the update:authtoken event.

All login methods converge on Login.login(), which calls this.ready() to fetch the user profile and emit the "ready" event.

Automatic Token Refresh Mechanism

LINEJS implements transparent token refresh that requires no manual intervention from consuming applications.

Detecting Token Expiration

The RequestClient in packages/linejs/base/request/mod.ts monitors server responses for the specific error code MUST_REFRESH_V3_TOKEN:

// From request/mod.ts (lines 24-28)
const isRefresh = Boolean(
  res.data.e &&
  res.data.e.code === "MUST_REFRESH_V3_TOKEN" &&
  await this.client.storage.get("refreshToken"),
);

When this error occurs and a refreshToken exists in storage, the client triggers automatic refresh.

The Refresh Token Flow

AuthService.tryRefreshToken() in packages/linejs/base/service/auth/mod.ts handles the exchange:

// From auth/mod.ts (lines 20-34)
public async tryRefreshToken() {
  const refreshToken = await this.client.storage.get("refreshToken");
  if (typeof refreshToken === "string") {
    const RATR = await this.refresh({ request: { refreshToken } });
    this.client.authToken = RATR.accessToken;
    this.client.emit("update:authtoken", RATR.accessToken);
    await this.client.storage.set(
      "expire",
      (RATR.tokenIssueTimeEpochSec as number) +
      (RATR.durationUntilRefreshInSec as number) as number,
    );
  } else {
    throw new InternalError("RefreshError", "refreshToken not found");
  }
}

This method calls the LINE endpoint /EXT/auth/tokenrefresh/v1, updates the in-memory authToken, emits the change event, and stores the new expiration timestamp.

Request Retry Logic

After successful refresh, RequestClient.requestCore() automatically retries the original request with the new token:

// From request/mod.ts (lines 51-62)
if (isRefresh && !isReRequest) {
  await this.client.auth.tryRefreshToken();
  return this.requestCore(
    path, value, methodName, protocolType,
    appendHeaders, overrideMethod, parse, true,
  );
}

The true flag passed as the final argument marks this as a re-request, preventing infinite refresh loops.

Implementing Token Event Listeners

Applications should listen to the update:authtoken event to persist tokens or update UI state:

client.on("update:authtoken", (newToken) => {
  // Update UI, store in secure storage, etc.
  console.log("Access token refreshed:", newToken);
});

The end event signals complete session termination when requests fail with NOT_AUTHORIZED_DEVICE, indicating the refresh token itself is invalid and manual re-authentication is required.

Summary

  • Dual-token architecture: LINEJS stores a short-lived authToken for requests and a long-lived refreshToken for renewal.
  • Automatic detection: The client monitors for MUST_REFRESH_V3_TOKEN errors in packages/linejs/base/request/mod.ts.
  • Seamless refresh: AuthService.tryRefreshToken() in packages/linejs/base/service/auth/mod.ts exchanges the refresh token and retries failed requests automatically.
  • Event-driven updates: The update:authtoken event notifies applications of token changes for persistence or UI updates.

Frequently Asked Questions

How does LINEJS detect when an access token expires?

LINEJS detects expired tokens by monitoring HTTP responses for the specific error code MUST_REFRESH_V3_TOKEN. When RequestClient in packages/linejs/base/request/mod.ts receives this error and finds a stored refreshToken, it automatically triggers the refresh flow without throwing an error to the calling code.

What happens if the refresh token is invalid or missing?

If the refresh token is missing or invalid, AuthService.tryRefreshToken() throws an InternalError with the code "RefreshError". Additionally, if a request fails with NOT_AUTHORIZED_DEVICE, the client emits the "end" event, signaling that the session has terminated and the user must re-authenticate manually.

Can I manually trigger a token refresh in LINEJS?

Yes, you can manually trigger a refresh by calling client.auth.tryRefreshToken(). This method is public and available in packages/linejs/base/service/auth/mod.ts. It retrieves the stored refresh token, calls the LINE /EXT/auth/tokenrefresh/v1 endpoint, and updates the client's authToken while emitting the update:authtoken event.

How should I persist authentication tokens between application restarts?

Listen to the update:authtoken event to save the access token, and ensure your storage backend persists the refreshToken (stored under the key "refreshToken"). When initializing the client, populate the storage with the saved refresh token so that AuthService.tryRefreshToken() can function if the access token expires during the session.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →