Next.jsにLINEのapiを用いてログインを組み込む


# コンソール画面での設定
CreateでProviderを作成します。

![スクリーンショット 2025-01-17 191950.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3439167/7f10fd32-678b-1dc8-bbef-ffbcadd83334.png)

ログインチャンネルを作成します。
![スクリーンショット 2025-01-17 192236.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3439167/9c8a6463-1869-8465-b333-3cfddf8b34e6.png)

以下のように必要事項を入力します。
![FireShot Capture 016 - LINE Developers - developers.line.biz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3439167/9e7453ed-1848-4f33-dfae-ba847f441826.png)

以上でチャンネルの作成が完了しました。

## ログインをする
こちらにある例を参考に進めていきます。

https://developers.line.biz/ja/docs/line-login/integrate-line-login/#making-an-authorization-request

client_idはChannel IDを使用します。
![スクリーンショット 2025-01-17 194207.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3439167/deb04071-1e7d-804f-c9fd-43dfdc60988f.png)

redirect_uriはコンソールで登録したコールバックURLにします。
![スクリーンショット 2025-01-17 194343.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3439167/9fc624f5-0f51-b94a-d051-46a102e007ea.png)

stateはひとまずサンプルと同じに「12345abcde」とします。
scopeは「profile%20openid%20email」にします。
そして、以下のようにLinkを作ります。
ここではNext.jsで使用します。
```react
<Link href='https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id=${CHANNNEL_ID}&redirect_uri=${CALLBACL_URL}&state=12345abcde&scope=profile%20openid%20email'>
Lineログイン
</Link>
```
以下の画面にリダイレクトされるのでログインをします。
![スクリーンショット 2025-01-17 200928.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3439167/54fd8ee5-f19e-8594-43d1-a9c98ec5fd02.png)

その後、redirect_uriに設定したURLにリダイレクトされます。その時URLにcodeとstateが付されます。
```
${redirect_uri}?code=${code}&state=12345abcde
```
上記URLのcode部分を取り出してlineLoginServerComponent内でレスポンスに含まれるrefresh tokenをクッキーとして格納します。
次に、lineLoginServerComponentのレスポンスに含まれるaccess_tokenを利用してgetLinProfileServerComponentを実行してユーザーのプロフィールを取得します。
```react:リダイレクト先
'use client';
import { lineLoginServerComponent } from '@/app/actions/set-cookie';
export default function App() {
  useEffect(() => {
    lineLogin();
  }, []);
  const lineLogin = async () => {
    const params = new URLSearchParams();
    if (searchParams.get('code') != null) {
      params.append('code', searchParams.get('code')!);
      const data = await lineLoginServerComponent(searchParams.get('code')!);
      console.log(data);
      const profile = await getLinProfileServerComponent(data.access_token);
      console.log(profile);
      setUser({ ...user, uid: profile.userId, displayName: profile.displayName });
    }
  };
}

```
client_secretはchannelのChannel secretを使用します。
![スクリーンショット 2025-01-18 163959.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3439167/54fcf9fb-4dee-d567-7b26-0312843db6df.png)



```react:app/actions/set-cookie.tsx
export async function lineLoginServerComponent(searchParams: string) {
  try {
    const params = new URLSearchParams();
    params.append('grant_type', 'authorization_code');
    params.append('code', searchParams);
    params.append('redirect_uri', `${process.env.NEXT_PUBLIC_LINE_REDIRECT_URI}`);
    params.append('client_id', `${process.env.NEXT_PUBLIC_LINE_CLIENT_ID}`);
    params.append('client_secret', `${process.env.NEXT_PUBLIC_LINE_CLIENT_SECRET}`);

    const requestOptions = {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: params.toString(),
    };

    const response = await fetch('https://api.line.me/oauth2/v2.1/token', requestOptions);

    const data = await response.json();
    if (response.ok) {
      console.log(data);

      await setCookieAction(data.refresh_token, data.expires_in);
      return data;

    } else {
      console.log('Error:', data);
    }
  } catch (error) {
    console.error('Error during LINE login:', error);
  }
}

export async function getLinProfileServerComponent(access_token: string) {
  try {
    const requestOptions = {
      method: 'GET',
      headers: {
        Authorization: `Bearer ${access_token}`,
      },
    };

    const response = await fetch('https://api.line.me/v2/profile', requestOptions);

    const data = await response.json();
    if (response.ok) {
      console.log(data);
      return data;
    } else {
      console.log('Error:', data);
    }
  } catch (error) {
    console.error('Error fetching profile:', error);
  }
}
export async function setCookieAction(token: string, expire: number): Promise<void> {
  await cookies().set({
    name: 'line_refresh_token',
    value: token,
    httpOnly: true,
    domain: `${process.env.NEXT_PUBLIC_COOKIE_DOMAIN}`,
    maxAge: 7776000,
    sameSite: 'lax',
    secure: true,
    path: '/',
  });
}

```
以上でログイン、プロフィールの取得が完了です。
# ログインを維持する
次に、リロードなどした際にもログインを維持する処理をcontextに組み込みます。
```react:@/context/AuthContext.tsx
'use client';
import {
  getLinProfileServerComponent,
  updateTokenServerComponent,
} from '@/app/actions/set-cookie';
  useEffect(() => {
    getProfile();
  }, []);
  const getProfile = async () => {
    const data = await updateTokenServerComponent();
    console.log(data);
    if (data) {
      const profile = await getLinProfileServerComponent(data.access_token);
      console.log(profile);
      setUser({ ...user, uid: profile.userId, displayName: profile.displayName });
    }
  };
```
```react:app/actions/set-cookie.tsx
export async function updateTokenServerComponent() {
  try {
    const hasCookie = await haveCookieAction(); // Assuming haveCookieAction is defined elsewhere
    if (!hasCookie) {
      throw new Error('No cookie found');
    }

    const params = new URLSearchParams();
    params.append('grant_type', 'refresh_token');
    params.append('refresh_token', hasCookie.value);
    params.append('client_id', `${process.env.NEXT_PUBLIC_LINE_CLIENT_ID}`);
    params.append('client_secret', `${process.env.NEXT_PUBLIC_LINE_CLIENT_SECRET}`);

    const requestOptions = {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: params.toString(),
    };

    const response = await fetch('https://api.line.me/oauth2/v2.1/token', requestOptions);

    const data = await response.json();
    if (response.ok) {
      console.log(data);
      await getLinProfileServerComponent(data.access_token);
      await setCookieAction(data.refresh_token, data.expires_in);
      return data;
    } else {
      console.log('Error:', data);
    }
  } catch (error) {
    console.error('Error updating token:', error);
  }
}

```
# 参考
https://developers.line.biz/ja/docs/line-login/integrate-line-login/#login-flow