line / line/line-sdk-android

An abandoned consent screen causes the next authorization request to be rejected with `access_denied` (LINE SDK 5.10.1)

Open
#193 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
149
Forks
54
PR merge metrics
No merged PRs in 30d

Description

## Summary

When integrating LINE Login with the LINE Android SDK, a login attempt that the user leaves without answering the consent screen puts the LINE application into a state where the **next** authorization request is rejected.

This does not require the user to close or kill anything. Simply pressing **Home**, switching to another application, or opening Recents and moving to a different task while the consent screen is displayed is enough. The LINE application keeps running, and the consent screen itself is never dismissed.

The next request never shows any UI. It returns `access_denied` almost immediately, and the SDK reports `LineApiResponseCode.CANCEL`. Only the request after that succeeds.

From the user's point of view the login button does nothing the first time it is pressed. From the integrating application's point of view the event is indistinguishable from the user declining consent.

---

## Environment

- LINE Android SDK: **5.10.1**
- LINE application: **26.13.0**
- Login method: App-to-App LINE Login (`botPrompt=aggressive`, scopes `profile email openid`)
- Result handling: `ActivityResultContracts.StartActivityForResult`
- Device:
- **Samsung SM-S931N**
- Android 16 (API 36)
- Reproducibility:
- Always reproducible once the consent screen has been abandoned

---

## Steps to Reproduce

1. Launch LINE Login from the host application. The LINE application opens the consent screen.
2. Without tapping **Allow** or **Cancel**, leave LINE with the **Home button** and return to the host application. The SDK cancels the attempt after about one second, as expected (`LineAuthenticationController.CANCEL_DELAY_MILLIS`).
LINE is only backgrounded here — it is not closed, not swiped away from Recents, and its process stays alive. Switching to another application instead of pressing Home produces the same result.
3. Start LINE Login again.
4. Start LINE Login once more.

---

## Expected Behavior

In step 3, the new authorization request should supersede the abandoned one and the consent screen should be displayed.

---

## Actual Behavior

In step 3:

- The LINE application does not become visible at all.
- `access_denied` is returned after roughly 370 ms.
- The SDK reports `LineApiResponseCode.CANCEL`.
- The callback carries the same parameters as a genuine consent denial.

In step 4 the login proceeds normally. Exactly one request is consumed each time the consent screen is abandoned.

---

## Investigation

All components named below belong to the LINE application package (`jp.naver.line.android`) and were read from its APK manifest and from `dumpsys` on a device. None of them are part of the SDK or of the integrating application.

### The abandoned consent screen is paused, not destroyed

`dumpsys activity activities`, immediately after step 2:

```
Task{#5892 A=jp.naver.line.android visible=false visibleRequested=false sz=2}
mLastPausedActivity: ChannelPermissionApprovalActivity t5892
* Hist #0: ChannelPermissionApprovalActivity
resultTo=OAuthWebLoginServiceActivity resultWho=null resultCode=100
windows=[Window{...ChannelPermissionApprovalActivity}]
```

The activity is still alive and still owes a result to its parent.

### The user cannot return to it

The OAuth task is excluded from Recents. From the LINE application manifest:

| Activity | `launchMode` | `excludeFromRecents` |
| --- | --- | --- |
| `jp.naver.line.android.urlscheme.service.oauth.OAuthWebLoginServiceActivity` | `2` (singleTask) | `true` |
| `jp.naver.line.android.activity.channel.permission.ChannelPermissionApprovalActivity` | (none → standard) | (none) |

Confirmed at runtime:

```
Recent tasks visible drop reason:
* Task{... A=jp.naver.line.android}
Drop Reason: ACTIVITY_EXCLUDE_FLAG

mCallingPackage=jp.naver.line.android
intent={flg=0x10800000 cmp=.../OAuthWebLoginServiceActivity}
0x10000000 FLAG_ACTIVITY_NEW_TASK
0x00800000 FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
```

Because `excludeFromRecents` is set while `noHistory` is not, the consent screen remains alive but unreachable. It is not listed in Recents, and the launcher icon opens LINE's main task instead. The user has no way to complete or dismiss the pending consent.

### The next request is answered by the previous attempt's activity

`logcat ActivityTaskManager` during step 3:

```
17:29:58.851 START .../LineAuthenticationActivity
from uid 10566 (host application)

17:29:59.222 START .../LineAuthenticationCallbackActivity
dat=lineauth://result/...
from uid 10595 (jp.naver.line.android) sr=18359843
```

`sr=18359843` identifies the activity that sent the callback:

```
ActivityRecord{18359843 jp.naver.line.android/.urlscheme.service.oauth.OAuthWebLoginServiceActivity t5892}
```

This belongs to `t5892` — the task created in step 1. No new LINE task was created for the step 3 request, and `Task{#5892}` disappears entirely right afterwards.

State across the three points:

| | Consent screen visible | After Home button | After the step 3 request |
| --- | --- | --- | --- |
| `Task{#5892}` | `visible=true` | `visible=false` | gone |
| `ChannelPermissionApprovalActivity` | `topResumedActivity` | `mLastPausedActivity` | gone |
| `resultCode` | `100` | `100` | — |

### A possible explanation

`OAuthWebLoginServiceActivity` is declared `singleTask`. When a new authorization intent is routed to it, the platform destroys every activity stacked above the existing instance before delivering `onNewIntent()`.

The activity destroyed that way is `ChannelPermissionApprovalActivity`, which was started with `startActivityForResult` and had a result pending. Its destruction delivers `RESULT_CANCELED` to `OAuthWebLoginServiceActivity`.

The instance therefore receives the new authorization request and a cancellation of the previous consent at effectively the same moment, and the cancellation appears to take precedence.

Two observations are consistent with this:

- The error is specifically `access_denied`, the code for a declined consent, rather than a generic failure.
- The SDK validates `state` before parsing `error` in `BrowserAuthenticationApi#getAuthenticationResultFrom`, and the result is a clean `CANCEL` rather than `Illegal parameter value of 'state'`. The new request's `state` was therefore adopted, which suggests the new intent was read before the flow was discarded.

We can only observe this from outside the LINE application, so this part is an inference rather than a confirmed cause.

---

## Impact

Integrating applications cannot distinguish this from a real consent denial. The callback parameters are identical, and the SDK maps both to `LineApiResponseCode.CANCEL`. Any application that measures its login funnel will count these as user denials.

There is also no way for an integrator to detect or clear the pending state, since the task belongs to the LINE application and is excluded from Recents.

---

## Questions

1. Is this behaviour expected when a previous authorization has been left unanswered?
2. Should a new authorization request supersede a pending one, rather than being rejected by it?
3. Is there a supported way for an integrating application to detect or reset this state?
4. Is there a recommended implementation pattern for applications where users frequently switch away during consent?

---

## Additional Notes

The 4.0.2 release notes list a known issue with the same user-visible symptom — `onActivityResult` returning `CANCEL` on the first login attempt with app-to-app login, succeeding on the second — which was fixed in 4.0.4 and scoped to Android 4.x. The cause described here appears to be different, but the symptom users experience is the same.

Possibly related open issues: #138, #140, #171, #192.

Thank you.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the abandoned-consent sequence on the stated Android environment, then read BrowserAuthenticationApi#getAuthenticationResultFrom and the LineAuthenticationController cancellation timing mentioned in the report. The issue names no repository file or test; done would require confirming the cause and documenting or implementing a supported way for a new request to supersede or reset the abandoned authorization.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, java
Domain
authentication, mobile
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.