every Tech Blog

株式会社エブリーのTech Blogです。

2026年卒エンジニア向け内定者研修を実施しました

2026年卒エンジニア向け内定者研修を実施しました

目次

はじめに

こんにちは。 開発本部開発1部トモニテ開発部所属の庄司(@ktanonymous)です。
弊社では、内定者向けとしては2回目となる内定者研修を2026年新卒のエンジニア内定者向けに実施し、 2025年中に全ての講義を完了しました。 今回の記事では、その内容について紹介したいと思います。

エンジニア内定者研修について

弊社では昨年度、エンジニア向けの内定者研修を初めて実施しました。 (昨年度の内定者研修の詳細については以下の記事をご覧ください)

tech.every.tv

昨年度の研修では、入社後の新卒研修をよりスムーズに進められるよう、受講者の知識のベースラインを揃えることを目的として実施しました。 今年度も引き続き同様の目的のもと、昨年度の経験やフィードバックを活かしてカリキュラムを改善し、2回目の内定者研修を実施しました。

エンジニア内定者研修の概要

ここでは、エンジニア内定者研修の目的やカリキュラム、それぞれの講義概要について紹介したいと思います。

エンジニア内定者研修の目的

先述の通り、内定者研修では来年度入社する新卒のエンジニアメンバーが、入社後の研修を通じてよりスムーズに開発組織にジョインできるように、 ベースとなる基礎知識を学べる機会を提供することを主な目的としています。
具体的には、以下のような目的と方針を設定しました。

目的
入社前に基本的な技術や知識をキャッチアップする環境を提供する

方針

  • 入社前に身に着けてほしい技術や知識のキャッチアップをサポートする
  • 基礎知識を早期にキャッチアップすることで入社後の研修・オンボーディングをよりスムーズに進められるようになる

上記の目的と方針を踏まえ、4月に入社した新卒メンバーが中心となり資料の作成から当日の講義までを担当してもらいました。 最終的な資料としては社内メンバーのレビューを経て内容を担保するようにしています。

エンジニア内定者研修カリキュラム

今回の研修では以下のテーマで講義を行いました。 各回 90 分を目安に、2 週間に 1 回程度のペースで実施しました。
今回は、昨今のAI技術の発展に伴う開発環境の変化を踏まえて、AI開発基礎を新たなテーマとして取り入れました。

  1. ターミナルおよび Git/GitHub の基礎・プログラム基礎
  2. ネットワーク/インフラ基礎
  3. DB 研修
  4. Web 基礎・Web アプリケーション開発基礎
  5. AI開発基礎

また、遠方から参加する方もいるため、全ての講義はオンラインで実施して録画を残すようにしました。
さらに、今回は講義で使用した資料を以下のリンクの speakerdeck に公開しています。 講義資料だけでなくイベントの登壇などで使用した資料などもご確認いただけますので、この機会にぜひご覧ください。

speakerdeck.com

前回からの改善点

前回の研修では受講者から以下のようなフィードバックがありました。

  • エンジニアとして知っておいた方が良いことを知ることができた
  • 開催時期に対してスケジュールがタイトに感じられた

また、運営面では担当の負荷が偏っていたり講義の準備のスケジュール感に対するフィードバックがありました。

これらのフィードバックを踏まえ、今回の研修では以下のような変更を取り入れました。

  • テーマ設定は昨年度の良さを引き継ぎつつ、関連性の高かったテーマを統合するとともに、昨今の開発環境の変化を踏まえてAI開発基礎を新たなテーマとして取り入れました。
  • 約半年間の研修スケジュールで間延びしないように2025年内に全ての講義を完了するようにしました。
  • 担当者の負荷が偏らないようにテーマごとの割り振りを均等に調整し、サポート体制なども明確になるように整備しました。

ターミナルおよび Git/GitHub の基礎・プログラム基礎

speakerdeck.com

ターミナルおよび Git/GitHub の基礎・プログラム基礎の講義では、2024年度の「ターミナルおよび Git/GitHub の基礎」と「プログラム基礎」を統合し、 CLI(ターミナル)やチームでの開発を行うにあたり弊社でも利用している Git/GitHub の基本的な使い方、およびプログラムの基本的な構造やデータ構造、アルゴリズムについて学びました。 具体的には以下のトピックを取り上げました。

  • Linux
  • コマンド
  • Git とは?
  • 木構造
  • 配列とリスト
  • ハッシュ
  • ソート
  • 探索

ネットワーク/インフラ基礎

speakerdeck.com

ネットワーク/インフラ基礎の講義では、OSI 参照モデルを中心に、 ネットワークやインフラの基礎知識について学びました。
具体的には以下のトピックを取り上げました。

  • プロトコル
  • TCP/IPとOSI参照モデル
  • 関連するAWSリソース

DB 研修

speakerdeck.com

DB 研修では、DB の基本概念やバックエンド/データ系それぞれの視点での利用について学びました。 具体的には以下のトピックを取り上げました。

  • 「データ」の種類と構造
  • SQLによるデータ操作
  • 正規化・インデックス
  • データ基盤の概要

Web 基礎・Web アプリケーション開発基礎

speakerdeck.com

Web 基礎・Web アプリケーション開発基礎の講義では、2024年度の「Web 基礎」と「Web アプリケーション開発基礎」を統合し、 API や Web アプリケーションの基本構成や仕組み、バックエンド/フロントエンドそれぞれの役割、アーキテクチャやテスト、コーディング時に意識することなど、 組織/チームでの開発に携わるうえで重要となってくる考え方について学びました。 具体的には以下のトピックを取り上げました。

  • Web アプリケーションの構成要素
    • ブラウザが表示するHTMLの取得元について
    • 外部データソースを利用した動的レンダリングについて
  • フロントエンドとバックエンドの役割
  • 開発に必要な知識
    • アーキテクチャ
    • テスト
    • CI/CD

AI開発基礎

speakerdeck.com

AI開発基礎の講義では、AIの概要やAIを活用した開発について学びました。 具体的には以下のトピックを取り上げました。

  • AIの基礎知識
    • AIの定義と分類
    • 言語モデル(LLM)の仕組み
    • トークン化とコンテキスト長
    • 埋め込み(Embeddings)
  • LLMの要素技術
    • プロンプトエンジニアリング
    • 推論時のプロンプト手法
    • RAG(検索拡張生成)技術
    • LLMの拡張・統合技術
    • コンテキストエンジニアリング
  • 開発ツールと活用事例の紹介

受講者のフィードバック

研修の改善のために、受講者からのフィードバックをアンケートで収集しており、その中でも以下のようなポジティブな意見が見受けられました。

  • 図解や具体例(例:デリッシュキッチンだとどうか)が豊富だったため、とてもイメージしやすかった
  • 昨今のAIによる開発環境の変化や具体的なモデルの構造などについて振り返ることができてよかった
  • 基本的なことが丁寧に解説されていて分かりやすかった

一方で、「内容のボリュームに対して時間がタイトに感じられた」「実例や背景をより詳細に説明してほしい」といった意見もあり、 テーマに対して取り扱う内容の範囲を適切に設定する難しさを改めて実感しました。
得られたフィードバックを踏まえ、今後の研修運営をさらにブラッシュアップしていきたいと考えています。

講義風景①
講義風景①
講義風景②
講義風景②

おわりに

今回の記事では、エンジニア内定者向けの研修についてご紹介いたしました。
内定者研修を通じて、今後入社するエンジニアのメンバーが入社後のオンボーディングをよりスムーズに進められるようにサポートすることができたと考えております。 また、研修の企画・運営に携わった若手メンバーにとっても、 知識の整理や研修の主要メンバーとしての新たなチャレンジの機会となり、貴重な成長の場にできたと感じています。

今回のような取り組みを含めて、今後もエンジニアの成長を支援する取り組みを続けて発信していきたいと思います。

最後まで読んでいただき、ありがとうございました。

Databricks DATA + AI WORLD TOUR に参加しました!

目次

はじめに

2025年11月28日に開催された「Databricks DATA + AI WORLD TOUR」に参加させていただきました。

今回は参加レポートとして、セッションの感想をお届けします!

dataaisummit.databricks.com

セッション紹介

zerobus Ingest

開発1部の吉田です。
Databricks Sessionで紹介されたzerobus ingestについてまとめます。

zerobus ingestは、クライアントからDatabricksへデータを直接ストリーミングできるマネージドサービスです。
従来のような複雑なメッセージバス(KafkaやKinesisなど)を介することなく、レコード単位でのデータ取り込みを実現します。

docs.databricks.com

中間インフラを排除できるため、運用管理の手間とコストが削減できます。

エブリーでは現在、サイネージ端末やWebアプリケーションのログ収集のために専用のインフラを構築・運用しています。
このインフラをzerobus ingestで置き換えることができるか、検証を進めていく予定です。

Agent Bricks

こんにちは!
開発1部デリッシュキッチンの蜜澤です。
僕からは、Databricks Sessionで紹介されたAgent Bricksについてまとめます!

Agent BricksはノーコードでAIエージェントの構築、評価、最適化できる機能になっています!
AIエージェントを作成する際の下記のような課題に簡単に対応することができます。

  • 調整すべき項目が多すぎる
  • 評価するのが難しい
  • コストと品質のバランスを取るのが難しい

Agent Bricksではタスクを選び、エージェントの役割をざっくり指定するだけで自動でエージェントを作成してくれるので、調整するべき項目が少なくなっているようです。
ドキュメントを元に質問に答える「ナレッジアシスタント」や、要約・分類などのカスタムテキスト変換を行う「カスタムLMM」、Genieスペースとエージェントを統合する「マルチエージェントスーパーバイザー」などのユースケースがあるようです。
エージェントの評価に関しては、自動でベンチマークを作成し、エージェントを自動で最適化できるようです。
また、good/badのような評価しかできないツールもありますが、Agent Learning from Human Feedback(ALHF)によって、自然言語での指示に基づき、システムを自動調整することもできます。
コストと品質に関しては、「コスト最適化」モデルと「品質最適化」モデルのどちらかを選択することができ、多くの場合は両立も可能なため、自前で構築するよりも高品質かつ低コストなエージェントを作成できることが多いようです。

Agent Bricksを使用してAIエージェントを作成すると、自前で作成する際に課題となる点を簡単に解決し、気軽にAIエージェントを作成できるのが魅力だと感じました。

デモパートでは、マルチエージェントスーパーバイザーを使用して、複数のエージェントを簡単にまとめる方法をご紹介いただきました。
PowerPointやWord形式のドキュメントと役割を与えられたナレッジアシスタントと、テーブルを読み取るためのGenieスペースを登録することで、ユーザーからの質問に対して、ナレッジを元に必要な情報を考え、必要な情報をGenieがクエリを作成しテーブルから取得するというマルチエージェントが簡単に作成できていました。
個人的に特に便利だなと感じたのは、ナレッジを元に回答できないパターンの質問に関しては、ガイドラインを登録し回答を準備しておくことが簡単にできることでした。

2025年11月時点ではasia-northeast1リージョンではAgent Bricksはまだ使用できないのですが、使用できるようになるのがとても楽しみになりました!

[企業セッション] イオンにおけるマルチエージェントシステムの開発

開発1部の岩﨑です。私からはイオン様におけるマルチエージェントシステムの開発について紹介します。

イオン様ではマルチエージェントシステムを「業務特化型エージェント」と「顧客向けAIエージェント」に大別して紹介されていました。

中でも業務特化型エージェントでは、自然言語の質問に基づいたクエリを実行して情報抽出するエージェントが紹介されました。

具体的には、データ抽出エージェントや可視化エージェントといったそれぞれのタスクに対するエージェントがあり、それをまとめるマルチエージェントスーパーバイザーをユーザとLLMとのインターフェースとしておくような構造となっています。

これによってクエリの生成から可視化、整合性の評価まで一気通貫で実行するようなマルチエージェントシステムを構築しています。

このエージェントはコンテキストが曖昧だった場合は推測で答えるのではなく、ユーザに聞き返すような仕組みとなっています。

また大規模なクエリをAIが実行しないように、システムリソースを大量に消費するクエリが生成された場合にはエージェントが主体となって実行しない意思決定が行われるようになっていたり、クエリをキャッシュする戦略など大規模データを扱っている企業ならではの工夫点などもみられました。

クエリの実行はDatabricks GenieとUnity Catalogを使用してAPIとして公開することでシームレスかつ低レイテンシで連携することができます。
また、Unity Catalogはガバナンスを意識した設計になっているためAIエージェントによる不正なデータアクセスなどの心配もありません。

さらにエージェントを作って終わりではなく、Genieへのリクエストを収集、フィードバックの作成、最適化のループを回すことによって精度改善にも取り組んでいるそうです。

他にも色々なエージェントの工夫点が紹介されており、非常に有意義なセッションでした。

まとめ

今回のDatabricks DATA + AI WORLD TOURでは、zerobus IngestやAgent Bricksといった機能の紹介から企業様の活用事例まで、幅広いセッションを聴講することができました。

AIエージェントに関するセッションが多く、ノーコードでのエージェント構築から大規模な業務システムへの適用までAIエージェントの活用が急速に広がっていました。

今回得られた知見を活かし、Databricksの機能の検証やAIエージェントの活用を進めていきたいと思います。

最後に

エブリーでは、ともに働く仲間を募集しています。

テックブログを読んで少しでもエブリーに興味を持っていただけた方は、ぜひ一度カジュアル面談にお越しください!

corp.every.tv

最後までお読みいただき、ありがとうございました!

AppleとLINEのネイティブ認証をつくる(サーバー編)

AppleとLINEのネイティブ認証をつくる(サーバー編)

この記事は every Tech Blog Advent Calendar 2025 の 29 日目の記事です。

こんにちは!開発1部で食事管理アプリ ヘルシカ のサーバーサイドの開発をしている 赤川 です。約1ヶ月にわたって続いたアドベントカレンダーも最終日となりました。

本記事では、ヘルシカiOS で Apple と LINE のネイティブ認証を導入した経験をもとに、ネイティブ認証のサーバー側の実装と周辺知識、実装時の細かいポイントについてお話しします。

iOS側の実装については、昨日公開の AppleとLINEのネイティブ認証をつくる(iOS編) をご覧ください。

tech.every.tv

前提

ヘルシカではもともと Web アプリベースの認証方法が採用されており、アプリ内で WebView が開きそこでIDやパスワードを入力して サインアップ / サインイン を行います。以下、AppleやLINEのIDプロバイダーを外部IdP、ヘルシカの認証サーバーを単に認証サーバーと呼ぶことにします。認証サーバーではOpenID Connectに則った実装がされており、おおまかには以下のような流れです。

  • ログインボタンを押す
  • アプリ内のWebViewで外部IdP(AppleやLINE)の入力画面が開く
  • 入力成功後、外部IdPから認証サーバーにコールバック
  • 認証サーバーが外部IdPとToken Exchangeを行い、認証サーバーが外部IdPのトークンを受け取る
  • 外部IdPのトークンの検証に成功した後、ヘルシカAPIサーバー用のトークンを発行し、安全にClientに渡す

アプリはほぼWebViewを開くだけで、IdPとメインでやり取りするのはサーバーであることがわかります。

上記の形だと、例えばヘルシカにWebアプリが増えた、という場合でも同じエンドポイントが使え、認証サーバーの追加実装なしに拡張が可能なのがメリットと言えます。しかし、パスワードなどの入力は面倒ですし、忘れがちです。そこで、ユーザーにより簡単にサインアップ・サインインをしてもらえるように、Appleなら顔認証などiOSネイティブな方法、LINEならLINEアプリが開いてワンタップで認証できる方法を新しく採用することになりました。

Appleの認証画面

LINEの認証画面

アプリ側のAppleとLINEのネイティブ認証実装

(サーバー編)とタイトルに入れましたが、サーバー側の設計をするためにはまずアプリ側でネイティブ認証を実装する方法を知らなければなりません。AppleではASAuthorizationAppleIDProviderなどのクラス、LINEではLINEログインSDKというものが用意されています。

developer.apple.com

developers.line.biz

元のフローを思い出してみると、外部IdPのトークンを受け取るのは認証サーバーでした。しかし、上記を使用した場合、IDトークンを受け取るのはアプリになります。それぞれについて、少し深掘りしてみましょう。

Apple

LINE

  • LINEでは、 LoginManager を使います。LoginManager.shared.login でリクエストを送信しますが、Appleの時と違い nonce と state はSDK内で自動的に生成、検証されます。ただし、 nonce は独自に指定することも可能です。
  • 成功すると、LoginResultを受け取ります。この中にアクセストークンやIDトークンなどが含まれています。

比較すると、以下の2つが共通していることがわかりました。

  • リクエストには独自の nonce を設定することができる。
  • 成功すると、アプリがIDトークンなどを受け取ることができる。

IDトークンと nonce

サーバー側の設計に進む前に、IDトークンと nonce について復習しておきます。ご存知の方は無視して次のパートに進んでいただいて問題ありません。

まず、IDトークンは次のような形をしています。

eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.
ewogImlzcyI6ICJodHRwczovL3NlcnZlci5leGFtcGxlLmNvbSIsCiAic3ViIjogIjI0ODI4OTc2MTAwMSIsCiAiYXVkIjogInM2QmhkUmtxdDMiLAogIm5vbmNlIjogIm4tMFM2X1d6QTJNaiIsCiAiZXhwIjogMTMxMTI4MTk3MCwKICJpYXQiOiAxMzExMjgwOTcwLAogIm5hbWUiOiAiSmFuZSBEb2UiLAogImdpdmVuX25hbWUiOiAiSmFuZSIsCiAiZmFtaWx5X25hbWUiOiAiRG9lIiwKICJnZW5kZXIiOiAiZmVtYWxlIiwKICJiaXJ0aGRhdGUiOiAiMDAwMC0xMC0zMSIsCiAiZW1haWwiOiAiamFuZWRvZUBleGFtcGxlLmNvbSIsCiAicGljdHVyZSI6ICJodHRwOi8vZXhhbXBsZS5jb20vamFuZWRvZS9tZS5qcGciCn0.
NTibBYW_ZoNHGm4ZrWCqYA9oJaxr1AVrJCze6FEcac4t_EOQiJFbD2nVEPkUXPuMshKjjTn7ESLIFUnfHq8UKTGibIC8uqrBgQAcUQFMeWeg-PkLvDTHk43Dn4_aNrxhmWwMNQfkjqx3wd2Fvta9j8yG2Qn790Gwb5psGcmBhqMJUUnFrGpyxQDhFIzzodmPokM7tnUxBNj-JuES_4CE-BvZICH4jKLp0TMu-WQsVst0ss-vY2RPdU1MzL59mq_eKk8Rv9XhxIr3WteA2ZlrgVyT0cwH3hlCnRUsLfHtIEb8k1Y_WaqKUu3DaKPxqRi6u0rN7RO2uZYPzC454xe-mg

https://openid.net/specs/openid-connect-core-1_0.html#id_tokenExample

2つのドットがあり3つのパートに区切られていることがわかります。これらは前から順にヘッダー、ペイロード、署名と呼ばれていて、このような形式のトークンを署名付き JWT (JSON Web Token) と言います。それぞれについて詳しく見ていきましょう。

まずヘッダーを Base64url デコードすると、以下のようなJSONが得られます。

{"kid":"1e9gdk7","alg":"RS256"}

algはJWTの署名に使用されたアルゴリズムを表します。 kid は署名検証用の公開鍵の識別子で、公開鍵暗号方式の署名の場合に含まれます。type: "JWT" というフィールドが含まれている場合もあります。

次に、ペイロードを検証、デコードしてみると、だいたい以下のような JSON が得られます。

{
"iss": "https://server.example.com",
"sub": "248289761001",
"aud": "s6BhdRkqt3",
"nonce": "n-0S6_WzA2Mj",
"exp": 1311281970,
"iat": 1311280970,
"name": "Jane Doe",
"given_name": "Jane",
"family_name": "Doe",
"gender": "female",
"birthdate": "0000-10-31",
"email": "janedoe@example.com",
"picture": "http://example.com/janedoe/me.jpg"
}

下の方はプロフィール的な情報なので無視して、上側のフィールドの説明を書き込むと以下のようになります。

{
"iss": "トークンを発行したサーバーの識別子",
"sub": "ユーザーの識別子",
"aud": "トークンを受け取るアプリの識別子(クライアントIDなど)",
"nonce": "nonce(下で説明します)",
"exp": "トークンの有効期限(UNIXタイムスタンプ形式)",
"iat": "トークンの発行日時(UNIXタイムスタンプ形式)"
}

nonce フィールドがありますね! nonce があることで、どのような利点があるかを以下で説明します。

登場人物は以下です。

  • IdP: IDトークンを発行する
  • Relying Party: 認証のサービスを使う(アプリや、そのサーバー)
  • ユーザー: ログインしようとしている
  • 攻撃者: IDトークンを盗んで、不正にログインしようとしている

まず、 nonce がない場合です。

nonceがない場合

IDトークンが正しく、有効期限内であれば、誰でも何回でもログインできてしまうことがわかります。このような攻撃を、リプレイ攻撃と呼びます。

次に nonce がある場合です。

nonceがある場合

ユーザーのログイン後、保存されていた nonce はすでに削除されているので、攻撃者が盗んだIDトークンは使えなくなっています。 nonce が "Number used once" の略である、ということが納得できるかと思います。

最後に署名です。署名は、ヘッダーとペイロードから、IdPだけが持っている秘密鍵を使って計算、付与されます。IDトークンを受け取ったクライアントは、alg に書いてある方法に従って署名を検証します。ここで検証に失敗すれば、何者かによってヘッダーまたはペイロードが書き換えられているということになります。

サーバー側の実装

それでは、サーバー側の実装について考えていきましょう。結果的に、エンドポイントは2つになりました。

IDトークンの検証は nonce を含めサーバー側で行います。 LINEログインSDK のように nonce をアプリ側で検証をすることも可能ですが、結局それではIDトークンを何度も使えてしまい、サーバーから見ると nonce を含めていないのと同じ状態になってしまいます。そもそも、クライアントから送られてきたものをサーバーがそのまま信じることはよろしくありません。

そうすると、必然的に nonce の生成もサーバー側が行うことになります。 LINE と Apple の比較で、リクエストには独自の nonce を設定することができる、ということを確認したので、サーバー側から nonce を渡すエンドポイントを作れば良いですね。また、 nonce を生成してキャッシュするのにキーが必要なので、その認証認可セッションの ID もランダムに生成して返却します。これが1つ目のエンドポイントになります(わかりやすさのため、一部フィールドを省略しています)。

リクエスト

{
    app_id: "アプリの識別子",
    service_id: "IdPの識別子(line or apple)",
    type: "signup or signin"
}

レスポンス

{
    nonce: "nonce",
    session_id: "その認証認可セッションの識別子",
}

クライアント側はこの送られてきた nonce を使い、IdPからIDトークンを取得し、セッションIDとともにサーバーに送ります。これが2つ目のエンドポイントです。

リクエスト

{
    app_id: "アプリの識別子",
    session_id: "その認証認可セッションの識別子",
    id_token: "IDトークン",
    authorization_code: "認可コード(アクセストークンなどの取得に使う、Appleのみ)"
}

サーバー側はセッション ID からキャッシュしていた nonce を取り出し、IDトークンの検証時に一致を確認します。検証に成功後、サーバーのDBにユーザーを作成、または更新し、アプリのIDトークン、アクセストークン、リフレッシュトークンを発行して返却します。アプリのトークンの発行部分については Amazon Cognito のカスタム認証を使っているのですが、ここでは触れないことにします。

レスポンス

{
    access_token: "アプリのアクセストークン",
    id_token: "アプリのIDトークン",
    refresh_token: "アプリのリフレッシュトークン",
    expires_in: "トークンの有効期限"
}

nonce を使うことで、安全にIDトークンをやり取りしてネイティブ認証を実現できました!最終的な流れは以下の通りです。

ネイティブ認証のフロー

LINE の公式ドキュメントでも、同様の方法が推奨されています。図も付いていてわかりやすいので、合わせてご覧ください。

developers.line.biz

Appleの細かいポイント

Bundle ID と Service ID

Bundle ID はアプリ固有の識別子、 Service ID はWebアプリなどがサインインなどのWebサービスを使う際に使用する識別子です。

元々の Web アプリベースの認証方法では Service ID が使われていましたが、ネイティブ認証では Bundle ID が使われます。

この使い分けが必要になるのは、トークンの Exchange の処理、つまり、クライアントから送られた authorization_code を使ってアクセストークンやリフレッシュトークンを取得する処理です。

取得のためには専用のエンドポイントを叩きますが、リクエストのパラメータの一つに client_secret というのがあります。詳しい作成方法については以下を参照ください。

developer.apple.com

client_secret は署名した JWT で、 sub フィールドを持ちます。ここに入るものが、 Web アプリベースの認証なら Service ID 、ネイティブ認証なら Bundle ID となります(上記リンク先には App ID と書いてありますが、Bundle ID でも機能します)。

LINEの細かいポイント

API

SDKは自動で nonce や state を生成、検証してくれたりとなかなかリッチでしたが、APIも充実しています。特に、IDトークン検証用のエンドポイントが用意されています。

developers.line.biz

ローカルでIDトークンを検証するのは少しだけ大変なので、これは有難いです。今回はAppleとIDトークンの検証処理を共通化させたかったので採用しませんでしたが、LINEのみ実装する場合には良い選択肢かと思います。

IDトークンの検証アルゴリズム

LINEでは、ネイティブアプリと Web アプリで署名アルゴリズムが異なります。公式ドキュメントで以下のように記されています。

ネイティブアプリやLINE SDK、LIFFアプリに対してはES256(ECDSA using P-256 and SHA-256)が、ウェブログインに対してはHS256(HMAC using SHA-256)が返されます。

ES256 は公開鍵暗号方式、HS256 は共通鍵暗号方式で、ES256の方が鍵管理のリスクが低いです。私も実装後に知ったのですが、一般に MPA (Multiple Page Application) の Web アプリなどはAPIサーバーとIdPが同一管理下にあることが多く、クライアントで署名検証をすることがないため共通鍵暗号方式でも十分、ネイティブアプリや SPA (Single Page Application) の Web アプリは IdP と API サーバーが分離していることが多く、場合によってはクライアント側で検証をすることもあるため公開鍵暗号方式が推奨されている、という背景があるようです。

最後に

既に動いているサービスで安全な認証認可を実現するために、いろいろな記事、動画を参考にさせていただきました。受け売りですが、認証認可や課金などの実装は、いろいろな方の集合知の上に成り立つ類のものだと考えています。この記事がまた、これから認証認可を実装する方の一助となれば幸いです。

それでは少し早いですが、皆様、今年も1年お世話になりました。 来年もどうぞよろしくお願いいたします。

参考資料

AppleとLINEのネイティブ認証をつくる(iOS編)

この記事は every Tech Blog Advent Calendar 2025 の 28 日目の記事です。

はじめに

こんにちは!開発1部で食事管理アプリ ヘルシカ の開発をしている新谷です。これまでサーバーサイドを担当していましたが、直近ではiOS開発にも携わっています。

ヘルシカiOSでは、これまでWebViewベースの認証を採用していましたが、AppleとLINEのネイティブ認証を導入しました。ネイティブ認証では、Appleなら顔認証やパスコード、LINEならLINEアプリでのワンタップ認証が可能になり、ユーザー体験が大きく向上します。

本記事では、iOS側の実装について解説します。認証の仕組みやサーバー側の設計については、明日公開予定の「サーバー編」をご覧ください。

ネイティブ認証の全体像

ネイティブ認証のフローは以下のようになります。

ポイントは、認証サーバーが生成したnonceをSDKに渡すことです。これにより、サーバー側でID Tokenの検証時にリプレイ攻撃を防ぐことができます。nonceの役割や検証の詳細については、明日の「サーバー編」で解説します。

Sign in with Appleの実装

Sign in with AppleにはAuthenticationServicesフレームワークを使用します。

developer.apple.com

ASAuthorizationAppleIDProviderの使い方

import AuthenticationServices

func signInWithApple(nonce: String) {
    let provider = ASAuthorizationAppleIDProvider()
    let request = provider.createRequest()
    request.requestedScopes = [.fullName, .email]
    request.nonce = nonce  // サーバーから取得したnonceを設定
    
    let controller = ASAuthorizationController(authorizationRequests: [request])
    controller.delegate = self
    controller.presentationContextProvider = self
    controller.performRequests()
}

Delegateでの結果受け取り

extension AppleNativeAuthProvider: ASAuthorizationControllerDelegate {
    func authorizationController(
        controller: ASAuthorizationController,
        didCompleteWithAuthorization authorization: ASAuthorization
    ) {
        guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential,
              let identityTokenData = credential.identityToken,
              let idToken = String(data: identityTokenData, encoding: .utf8),
              let authorizationCodeData = credential.authorizationCode,
              let authorizationCode = String(data: authorizationCodeData, encoding: .utf8)
        else {
            // エラーハンドリング
            return
        }
        
        // idToken と authorizationCode をサーバーに送信
    }
    
    func authorizationController(
        controller: ASAuthorizationController,
        didCompleteWithError error: Error
    ) {
        // ユーザーキャンセルやその他のエラー処理
    }
}

取得できるもの

Sign in with Appleからは以下の情報を取得できます。

項目 説明
ID Token JWTフォーマット。nonceが含まれる
Authorization Code サーバーでのトークン取得に使用
User Identifier ユーザーの一意な識別子
Full Name 初回認証時のみ取得可能
Email 初回認証時のみ取得可能

LINE SDKの実装

LINE LoginにはLINE SDK for iOS Swiftを使用します。

developers.line.biz

LINE SDKのセットアップ

Swift Package Managerで以下のURLを追加します。

https://github.com/line/line-sdk-ios-swift.git

Info.plistにも設定が必要です。

<key>LineSDKConfig</key>
<dict>
    <key>ChannelID</key>
    <string>YOUR_LINE_CHANNEL_ID</string>
</dict>

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>line3rdp.$(PRODUCT_BUNDLE_IDENTIFIER)</string>
        </array>
    </dict>
</array>

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>lineauth2</string>
</array>

LoginManagerの使い方

import LineSDK

func signInWithLine(nonce: String, from viewController: UIViewController) {
    LoginManager.shared.login(
        permissions: [.profile, .openID],
        in: viewController,
        parameters: .init(IDTokenNonce: nonce)  // サーバーから取得したnonceを設定
    ) { result in
        switch result {
        case .success(let loginResult):
            guard let idToken = loginResult.accessToken.IDToken else {
                // エラーハンドリング
                return
            }
            // idToken をサーバーに送信
            
        case .failure(let error):
            // ユーザーキャンセルやその他のエラー処理
        }
    }
}

Appleとの違い

LINE SDKとSign in with Appleの主な違いは、Authorization Codeの有無です。Appleではサーバーでのリフレッシュトークン取得にAuthorization Codeが必要ですが、LINEではリフレッシュトークンがSDK内部で管理されます。

共通点として、どちらも独自のnonceを設定でき、ID Tokenを取得できます。

最初の設計と問題点

クリーンアーキテクチャでの設計

ヘルシカiOSではクリーンアーキテクチャを採用しています。アーキテクチャの詳細についてはヘルシカiOSアプリのアーキテクチャについてをご覧ください。

当初、ネイティブ認証も既存のアーキテクチャに従って以下のように設計しました。

Feature層(ViewModel)
    ↓
UseCase層
    ↓
Repository層
    ↓
Infra層(SDK呼び出し)
    ↓
外部SDK(LINE SDK / AuthenticationServices)

問題点:認証処理がViewに影響を与える

実装を進める中で、この設計には問題があることがわかりました。

Sign in with Appleは ASAuthorizationController で認証処理を実行すると認証UIが表示され、ASAuthorizationControllerPresentationContextProviding で表示先のWindowを指定します。

// Sign in with Apple:認証UIを表示するためにWindowを指定
extension AppleAuthProvider: ASAuthorizationControllerPresentationContextProviding {
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        return window
    }
}

LINE SDKも同様に、認証処理を呼び出すとLINEアプリまたはWebViewが起動し、Viewに影響を与えます。

// LINE SDK:認証処理を呼び出すとLINEアプリまたはWebViewが起動
LoginManager.shared.login(
    permissions: [.profile],
    in: viewController,
    parameters: .init(IDTokenNonce: nonce)
)

つまり、これらの認証処理を呼び出すとViewレイヤーに影響を与えることになります。

Infra層は本来、外部APIやLocalStorageなど、UIに依存しない外部リソースへのアクセスを担当する層です。 認証処理がViewに影響を与えるものをInfra層に配置するのは、アーキテクチャとして適切でないと考えました。

解決策:NativeAuthパッケージの分離

この問題を解決するために、認証処理をクリーンアーキテクチャの外に独立したパッケージとして分離しました。

新しいアーキテクチャ

Feature層(ViewModel)
    │
    ├──────────────────────> NativeAuthパッケージ(独立)
    │                            ├── LineNativeAuthProvider
    │                            └── AppleNativeAuthProvider
    ↓
UseCase層
    ↓
Repository層
    ↓
Infra層

ポイント

  • NativeAuthパッケージをクリーンアーキテクチャとは独立した位置に配置
  • ViewModelから直接NativeAuthProviderを呼び出す構成に変更
  • UseCase/Repository/Infra層はサーバーとの通信(nonce取得、ID Token検証)に専念

この設計には、UIに依存する処理をInfra層に置かずに済み、認証処理を独立パッケージとして管理できるというメリットがあります。一方で、ViewModelが認証処理を直接呼び出すため、Feature層の責務が増えるというデメリットもあります。

ただ、Infra層にUI依存のコードを置くことの違和感の方が大きかったため、今回はこの設計を選びました。

まとめ

最近サーバーサイドからiOS開発も担当するようになったので、モバイルアプリ特有のアーキテクチャには苦戦しました。 特にViewはサーバーでは意識しない概念だったので、今後も適切な場所に配置できるよう気をつけていきたいです。

明日は「サーバー編」として、nonceの役割やID Tokenの検証など、サーバー側の実装についての記事が公開されます。ぜひそちらもご覧ください。

参考資料

私たちのLaravelプロジェクトにおけるGit hooks設定のご紹介

この記事は every Tech Blog Advent Calendar 2025 の 27 日目の記事です。

はじめに

こんにちは。リテールハブ開発部の清水です。

私たちは小売向けサービスをLaravelで開発しています。
このプロジェクトではGit hooksのpre-commit設定を使用してコミットのタイミングでLaravel Pint, Larastanを呼び出すことでコード品質を整えるための仕組みを使用しています。
この仕組みのベースは、プロジェクト初期に整備されたものを引き継いだもので、今回その内容を見直しながら整理しました。
ちょうど良い機会でしたので、本記事で私たちが使用している設定内容をご紹介いたします。

Git hooksとは?

Git hooksとは、git commit や git push などの Git 操作をきっかけに、自動でスクリプトを実行できる仕組みです。
コミット前にチェック処理を挟むなど、人の操作ミスを防ぐための自動処理を組み込む用途で使われます。
https://git-scm.com/docs/githooks

Laravel Pintとは?

Laravel Pint は、Laravel公式が提供している PHPコードの自動フォーマッターです。
決められたコーディング規約(Laravel / PSR-12 など)に従って、PHPコードの書き方を自動的に統一します。
https://laravel.com/docs/12.x/pint

Larastanとは?

Larastan は、PHP の静的解析ツール PHPStan を Laravel 向けに拡張したツールです。
コードを実行せずに解析し、存在しないプロパティや型の不整合などの問題を事前に検出します。
https://github.com/larastan/larastan

コミットを行った時の処理の流れ

  1. git commitを実行すると、Git hooksのpre-commitフックが起動
  2. コミット対象のPHPファイルを取得
  3. Laravel Pintによるフォーマットチェック
  4. Larastanによる静的解析
  5. 3 or 4のチェックでエラーが検出された場合、コミットを中断
  6. 全てのチェックをパスした場合のみ、コミットが完了

pre-commit設定内容

#!/bin/sh
set -eu

# --- 1) コミット対象(ステージ済み)のPHPファイルだけ拾う ---
php_files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.php$' || true)

# PHPファイルがなければ何もしない
if [ -z "$php_files" ]; then
  exit 0
fi

echo "コミット対象のPHPファイルをチェックしています..."

# --- 2) Pint:フォーマットチェック(修正はしない) ---
echo "Pintでフォーマットをチェックしています..."
if ! echo "$php_files" | xargs ./vendor/bin/pint --test; then
  echo ""
  echo "❌ フォーマットエラーがあります。"
  echo "   以下のコマンドで修正してください:"
  echo "   make lint"
  exit 1
fi
echo "✓ フォーマットチェック OK"

# --- 3) Larastan:静的解析 ---
if echo "$php_files" | grep -qE '^app/'; then
  echo "Larastanで静的解析を実行しています..."
  if ! ./vendor/bin/phpstan analyse --no-progress --memory-limit=1G; then
    echo ""
    echo "❌ 静的解析エラーがあります。"
    echo "   エラーを修正してから再度コミットしてください。"
    exit 1
  fi
  echo "✓ 静的解析 OK"
fi

echo ""
echo "✓ 全てのチェックが完了しました。"
exit 0

Makeコマンドの紹介

コミットのタイミングだけではなく、手動でLaravel Pint, Larastanを実行したい場面もあります。
以下のMakeコマンドで実行できるようにしています。

# コードスタイルチェック&修正
lint:
    @files=$$(git diff --cached --name-only --diff-filter=ACM | grep "\.php$$" | sed "s|^$$(basename $$(pwd))/||"); \
    if [ -n "$$files" ]; then \
        docker compose -f $(COMPOSE_FILE) exec -T $(CONTAINER_PHP) sh -c "./vendor/bin/pint $$files"; \
    else \
        echo "ステージされたPHPファイルがありません"; \
    fi

# コードスタイルチェック
lint-check:
    @files=$$(git diff --cached --name-only --diff-filter=ACM | grep "\.php$$" | sed "s|^$$(basename $$(pwd))/||"); \
    if [ -n "$$files" ]; then \
        docker compose -f $(COMPOSE_FILE) exec -T $(CONTAINER_PHP) sh -c "./vendor/bin/pint --test $$files"; \
    else \
        echo "ステージされたPHPファイルがありません"; \
    fi

# 静的解析
larastan:
    docker compose -f $(COMPOSE_FILE) exec $(CONTAINER_PHP) ./vendor/bin/phpstan analyse --memory-limit=1G

実際にコミットする流れ

Laravel Pint, Larastanで弾かれる内容のコードを作成

<?php

namespace App\Http\Controllers;

// importが名前順になっていない
use Retailapp\Common\Models\User;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;

class TestController extends Controller
{
    public function index(): JsonResponse
    {
        $user = User::find(1);
        $name = $user->undefined_property; // 存在しないプロパティを呼び出している

        return response()->json(['name' => $name]);
    }
}

コミットを試みると、フォーマットエラーで弾かれる

% git commit -m '弾かれてほしいコミット'
コミット対象のPHPファイルをチェックしています...

Pintでフォーマットをチェックしています...

  ⨯

  ──────────────────────────────────────────────────────────────────── Laravel  
    FAIL   ............................................. 1 file, 1 style issue  
  ⨯ app/Http/Controllers/TestController.php no_unused_imports, ordered_import…  

❌ フォーマットエラーがあります。
   以下のコマンドで修正してください:
   make lint

pintで自動的に修正

% make lint

  ✓

  ──────────────────────────────────────────────────────────────────── Laravel  
    FIXED   ...................................... 1 file, 1 style issue fixed  
  ✓ app/Http/Controllers/TestController.php no_unused_imports, ordered_import… 

もう一度コミットすると、今度はLarastanで弾かれる

% git commit -m '弾かれてほしいコミット'
コミット対象のPHPファイルをチェックしています...

Pintでフォーマットをチェックしています...

  ──────────────────────────────────────────────────────────────────── Laravel  
    PASS   ............................................................ 1 file  

✓ フォーマットチェック OK
Larastanで静的解析を実行しています...

 ------ -------------------------------------------------------------------------- 
  Line   Http/Controllers/TestController.php                                       
 ------ -------------------------------------------------------------------------- 
  :14    Access to an undefined property                                           
         Retailapp\Common\Models\User::$undefined_property.                        
         💡 Learn more:                                                            
            https://phpstan.org/blog/solving-phpstan-access-to-undefined-property  
 ------ -------------------------------------------------------------------------- 

 [ERROR] Found 1 error                                                          
                                                                                
❌ 静的解析エラーがあります。
   エラーを修正してから再度コミットしてください。

Larastanに違反する部分を修正

<?php

namespace App\Http\Controllers;

use Illuminate\Http\JsonResponse;
use Retailapp\Common\Models\User;

class TestController extends Controller
{
    public function index(): JsonResponse
    {
        $user = User::find(1);
        $name = $user->name; // 修正

        return response()->json(['name' => $name]);
    }
}

コミット成功

% git commit -m '通ってほしいコミット'
コミット対象のPHPファイルをチェックしています...

Pintでフォーマットをチェックしています...

  .

  ──────────────────────────────────────────────────────────────────── Laravel  
    PASS   ............................................................ 1 file  

✓ フォーマットチェック OK
Larastanで静的解析を実行しています..
                                                                                
 [OK] No errors                                                                 
                                                                                
✓ 静的解析 OK

✓ 全てのチェックが完了しました。

おわりに

本記事では、私たちの Laravel プロジェクトで使用している Git hookのpre-commit 設定と、その中で Laravel Pint・Larastan をどのように組み込んでいるかをご紹介しました。
同様の仕組みを検討されている方の参考になれば幸いです。

最後までお読みいただきましてありがとうございました。