Skip to content

feat: Add LINE id_token validation#10436

Open
swittk wants to merge 3 commits intoparse-community:alphafrom
swittk:feature/line-id-token-auth
Open

feat: Add LINE id_token validation#10436
swittk wants to merge 3 commits intoparse-community:alphafrom
swittk:feature/line-id-token-auth

Conversation

@swittk
Copy link
Copy Markdown

@swittk swittk commented Apr 14, 2026

Pull Request

Issue

Parse Server's LINE auth adapter only supported secure server-side code + redirect_uri exchange, or the deprecated insecure id + access_token flow. Unlike the Google / Apple / Facebook adapters, it did not support validating a secure client-provided token directly.

This blocks architecture where LINE login is handled by a separate auth service and the Parse Server should only validate the resulting LINE identity without holding the LINE client secret.

Approach

Added secure LINE OpenID Connect id_token validation to the LINE auth adapter.

  • the existing server-side code + redirect_uri flow continues working
  • adds secure id_token validation using LINE signing keys / supported algorithms
  • keeps the old allowsInsecureAuth flag path for legacy fallback (insecure id + access_token) when enabled
  • normalize LINE profile userId to id so the LINE adapter matches the BaseCodeAuthAdapter contract

Tasks

  • Add tests
  • Add changes to documentation (guides, repository pages, code comments)
  • Add security check
  • Add new Parse Error codes to Parse JS SDK

Summary by CodeRabbit

  • New Features

    • Secure id_token verification with ES256/HS256 support and issuer/id/nonce enforcement
    • Adapter will populate auth id from verified id_token during auth flows
  • Improvements

    • Require clientId for secure flows; explicit handling when clientSecret is missing (code exchange requires secret)
    • Returned user now includes normalized Parse-style id
  • Tests

    • Expanded unit and E2E tests for token verification, option validation, and auth flows

@parse-github-assistant
Copy link
Copy Markdown

parse-github-assistant bot commented Apr 14, 2026

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

Tip

  • Keep pull requests small. Large PRs will be rejected. Break complex features into smaller, incremental PRs.
  • Use Test Driven Development. Write failing tests before implementing functionality. Ensure tests pass.
  • Group code into logical blocks. Add a short comment before each block to explain its purpose.
  • We offer conceptual guidance. Coding is up to you. PRs must be merge-ready for human review.
  • Our review focuses on concept, not quality. PRs with code issues will be rejected. Use an AI agent.
  • Human review time is precious. Avoid review ping-pong. Inspect and test your AI-generated code.

Note

Please respond to review comments from AI agents just like you would to comments from a human reviewer. Let the reviewer resolve their own comments, unless they have reviewed and accepted your commit, or agreed with your explanation for why the feedback was incorrect.

Caution

Pull requests must be written using an AI agent with human supervision. Pull requests written entirely by a human will likely be rejected, because of lower code quality, higher review effort and the higher risk of introducing bugs. Please note that AI review comments on this pull request alone do not satisfy this requirement. Our CI and AI review are safeguards, not development tools. If many issues are flagged, rethink your development approach. Invest more effort in planning and design rather than using review cycles to fix low-quality code.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 14, 2026

📝 Walkthrough

Walkthrough

Added JWT-based LINE id_token verification, secure-options validation, and lifecycle integration (beforeFind) to enforce id/nonce/iss checks; updated code-exchange to require clientSecret, normalized user id mapping, and expanded test coverage and E2E tests for secure and legacy flows.

Changes

Cohort / File(s) Summary
Tests
spec/Adapters/Auth/line.spec.js
Added extensive unit and E2E tests: helper to mock ES256 id_token verification, validateOptions suite, verifyIdToken coverage (ES256/HS256 paths, claim checks), getAccessTokenFromCode/getUserFromAccessToken updates, beforeFind tests, and two E2E suites for id_token and legacy access_token flows.
Adapter implementation
src/Adapters/Auth/line.js
Introduced verifyIdToken (ES256 via LINE JWKS + HS256 via clientSecret), added beforeFind(authData) to verify id_token and populate authData.id, require clientSecret for code exchange, normalize user object to include id, and added validateOptions for secure-mode requirement of clientId.
Manifest / docs
manifest/… (manifest file edits)
Documentation and configuration contract updated to mark clientSecret optional and document when it's required; small manifest adjustments reflecting new behavior.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ParseAdapter as Adapter
  participant LINE_JWKS as "LINE JWKS (external)"
  participant JWTLib as "jwt.verify"
  note over Client,Adapter: Flow: login with id_token or code

  Client->>Adapter: send authData { id_token } / { code }
  alt id_token path
    Adapter->>JWTLib: parse header (kid, alg)
    alt alg == ES256
      Adapter->>LINE_JWKS: fetch JWKS (cached)
      LINE_JWKS-->>Adapter: jwk (public key)
      Adapter->>JWTLib: verify token with public key (alg: ES256)
    else alg == HS256
      Adapter->>JWTLib: verify token with clientSecret (alg: HS256)
    end
    JWTLib-->>Adapter: verified claims
    Adapter->>Adapter: validate iss, sub/id, nonce
    Adapter-->>Client: success (authData.id set, id_token removed)
  else code exchange path
    Adapter->>LINE_JWKS: (not used)
    Adapter->>Adapter: require clientSecret else error
    Adapter->>LINE_API: exchange code for access_token (server-side)
    LINE_API-->>Adapter: access_token
    Adapter->>LINE_API: fetch profile with access_token
    LINE_API-->>Adapter: profile
    Adapter-->>Client: normalized user { id, ... }
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Engage In Review Feedback ❓ Inconclusive Commit message indicates feedback was received and acted upon, but sandbox cannot access GitHub PR discussion threads or reviewer comments needed to verify substantive engagement. Visit the pull request at #10436 and review the Conversation tab and inline code comments to verify documented engagement with feedback before changes were implemented.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding LINE id_token validation, which is the primary objective of this PR.
Description check ✅ Passed The description covers all required sections with detailed information about the issue, approach, and completed tasks.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Security Check ✅ Passed JWT security implementation demonstrates strong practices with proper algorithm whitelisting, signature verification, and claim validation. No critical vulnerabilities detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@swittk swittk changed the title Support secure LINE id_token auth without server-side code exchange feat: add LINE id_token validation Apr 14, 2026
@parse-github-assistant
Copy link
Copy Markdown

I will reformat the title to use the proper commit message syntax.

@parse-github-assistant parse-github-assistant bot changed the title feat: add LINE id_token validation feat: Add LINE id_token validation Apr 14, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
spec/Adapters/Auth/line.spec.js (1)

15-34: Minor: JWT exp claim should be in seconds, not milliseconds.

The mock uses Date.now() + 1000 for the exp claim, but JWT expiration is specified in seconds since epoch, not milliseconds. While this doesn't affect test execution (since jwt.verify is mocked), using accurate mock data improves test clarity.

🔧 Proposed fix
   function mockEs256IdTokenVerification(claims = {}) {
     const jwtClaims = {
       iss: 'https://access.line.me',
       aud: 'validClientId',
-      exp: Date.now() + 1000,
+      exp: Math.floor(Date.now() / 1000) + 3600,
       sub: 'mockUserId',
       ...claims,
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/Adapters/Auth/line.spec.js` around lines 15 - 34, In
mockEs256IdTokenVerification update the jwtClaims.exp to be in seconds instead
of milliseconds by replacing Date.now() + 1000 with a seconds-based value (e.g.,
Math.floor(Date.now() / 1000) + 1) so the mocked exp claim correctly represents
seconds-since-epoch; modify the exp assignment inside the jwtClaims object in
mockEs256IdTokenVerification accordingly.
src/Adapters/Auth/line.js (1)

106-130: JWKS client is recreated on every call, defeating the cache.

The jwksClient instance is created fresh each time getLineKeyByKeyId is invoked. Since jwks-rsa maintains its cache per-client instance, every verifyIdToken call will fetch keys from LINE's JWKS endpoint anew rather than reusing cached keys.

Consider caching the client at the adapter instance level (similar to how other auth adapters handle JWKS clients).

♻️ Proposed refactor to cache the JWKS client
-const getLineKeyByKeyId = async (
-  /** `@type` {string} The JWT header `kid`. */
-  keyId,
-  /** Maximum number of cached JWKS entries */
-  cacheMaxEntries,
-  /** Maximum JWKS cache age in milliseconds */
-  cacheMaxAge) => {
-  const client = jwksClient({
-    jwksUri: 'https://api.line.me/oauth2/v2.1/certs',
-    cache: true,
-    cacheMaxEntries,
-    cacheMaxAge,
-  });
-
-  let key;
-  try {
-    key = await authUtils.getSigningKey(client, keyId);
-  } catch {
-    throw new Parse.Error(
-      Parse.Error.OBJECT_NOT_FOUND,
-      `Unable to find matching key for Key ID: ${keyId}`
-    );
-  }
-  return key;
-};
+let cachedJwksClient = null;
+let cachedJwksConfig = null;
+
+const getJwksClient = (cacheMaxEntries, cacheMaxAge) => {
+  const configKey = `${cacheMaxEntries}:${cacheMaxAge}`;
+  if (!cachedJwksClient || cachedJwksConfig !== configKey) {
+    cachedJwksClient = jwksClient({
+      jwksUri: 'https://api.line.me/oauth2/v2.1/certs',
+      cache: true,
+      cacheMaxEntries,
+      cacheMaxAge,
+    });
+    cachedJwksConfig = configKey;
+  }
+  return cachedJwksClient;
+};
+
+const getLineKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => {
+  const client = getJwksClient(cacheMaxEntries, cacheMaxAge);
+  let key;
+  try {
+    key = await authUtils.getSigningKey(client, keyId);
+  } catch {
+    throw new Parse.Error(
+      Parse.Error.OBJECT_NOT_FOUND,
+      `Unable to find matching key for Key ID: ${keyId}`
+    );
+  }
+  return key;
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Adapters/Auth/line.js` around lines 106 - 130, getLineKeyByKeyId creates
a new jwksClient on every call which defeats jwks-rsa caching; move creation of
the jwksClient to the adapter instance (e.g., this.jwksClient or a private
field) and reuse it from getLineKeyByKeyId, only constructing it once (lazily if
needed) with cache, cacheMaxEntries and cacheMaxAge applied; update
getLineKeyByKeyId to call authUtils.getSigningKey(this.jwksClient, keyId) (or
initialize this.jwksClient first) and keep the same Parse.Error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@spec/Adapters/Auth/line.spec.js`:
- Around line 15-34: In mockEs256IdTokenVerification update the jwtClaims.exp to
be in seconds instead of milliseconds by replacing Date.now() + 1000 with a
seconds-based value (e.g., Math.floor(Date.now() / 1000) + 1) so the mocked exp
claim correctly represents seconds-since-epoch; modify the exp assignment inside
the jwtClaims object in mockEs256IdTokenVerification accordingly.

In `@src/Adapters/Auth/line.js`:
- Around line 106-130: getLineKeyByKeyId creates a new jwksClient on every call
which defeats jwks-rsa caching; move creation of the jwksClient to the adapter
instance (e.g., this.jwksClient or a private field) and reuse it from
getLineKeyByKeyId, only constructing it once (lazily if needed) with cache,
cacheMaxEntries and cacheMaxAge applied; update getLineKeyByKeyId to call
authUtils.getSigningKey(this.jwksClient, keyId) (or initialize this.jwksClient
first) and keep the same Parse.Error handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1415cab2-f414-4834-b557-e23e50d5e794

📥 Commits

Reviewing files that changed from the base of the PR and between 31f70a3 and 1ed8064.

📒 Files selected for processing (2)
  • spec/Adapters/Auth/line.spec.js
  • src/Adapters/Auth/line.js

coderabbitai[bot]
coderabbitai bot previously approved these changes Apr 14, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@spec/Adapters/Auth/line.spec.js`:
- Around line 67-69: The test's expected error message does not match what the
adapter actually throws when id_token is missing; update the assertion in
spec/Adapters/Auth/line.spec.js that calls adapter.verifyIdToken({}) to expect
the adapter's actual message ("Line auth is invalid for this user.") instead of
"id token is invalid for this user.", so the call to adapter.verifyIdToken({})
and its rejection check match the thrown error string.
- Around line 78-84: The test currently stubs authUtils.getHeaderFromToken but
still allows verifyIdToken to perform a real signing-key lookup, making it
flaky; update the test to also stub the signing-key lookup (e.g.,
authUtils.getSigningKey or the method your adapter calls to fetch the public
key) to synchronously return null or throw a deterministic error so no network
request is made, then assert adapter.verifyIdToken({ id_token: 'the_token' }) is
rejected with 'Unable to find matching key for Key ID: 789'—this ensures the
missing-public-key path is fully offline and deterministic while still
referencing getHeaderFromToken and verifyIdToken.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d5aa1868-7af1-4b3f-8924-596de6f312a8

📥 Commits

Reviewing files that changed from the base of the PR and between 1ed8064 and a4fc483.

📒 Files selected for processing (1)
  • spec/Adapters/Auth/line.spec.js

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
spec/Adapters/Auth/line.spec.js (1)

87-97: Add a regression test for unsupported alg values.

You cover the positive ES256 guard well, but the explicit rejection branch for unsupported algorithms (e.g., alg: 'none' or RS256) should be locked with a dedicated test to prevent security regressions.

Suggested test addition
+    it('should reject unsupported id_token signing algorithms', async function () {
+      spyOn(authUtils, 'getHeaderFromToken').and.returnValue({ kid: '123', alg: 'none' });
+
+      await expectAsync(adapter.verifyIdToken({ id_token: 'the_token' })).toBeRejectedWithError(
+        'Unsupported Line id_token signing algorithm: none'
+      );
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/Adapters/Auth/line.spec.js` around lines 87 - 97, Add a regression test
that sends an id_token whose JWT header contains an unsupported alg (e.g.,
"none" or "RS256") and asserts adapter.verifyIdToken rejects/throws rather than
accepting it; locate the test suite that calls adapter.verifyIdToken (the
existing spec using mockEs256IdTokenVerification) and add a new it() that
stubs/mock the token header to have alg 'none' (or 'RS256'), then assert the
promise rejects (or throws) and that jwt.verify is not invoked with an unsafe
algorithm (or is not called at all), referencing adapter.verifyIdToken and
jwt.verify in your assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@spec/Adapters/Auth/line.spec.js`:
- Around line 87-97: Add a regression test that sends an id_token whose JWT
header contains an unsupported alg (e.g., "none" or "RS256") and asserts
adapter.verifyIdToken rejects/throws rather than accepting it; locate the test
suite that calls adapter.verifyIdToken (the existing spec using
mockEs256IdTokenVerification) and add a new it() that stubs/mock the token
header to have alg 'none' (or 'RS256'), then assert the promise rejects (or
throws) and that jwt.verify is not invoked with an unsafe algorithm (or is not
called at all), referencing adapter.verifyIdToken and jwt.verify in your
assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e0940f60-e5a1-4be1-abc8-e32bb744c316

📥 Commits

Reviewing files that changed from the base of the PR and between a4fc483 and 74a8ba2.

📒 Files selected for processing (1)
  • spec/Adapters/Auth/line.spec.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant