Developer's Blog

すべての PC で Windows ストアアプリに一発ログイン!シングルサインオンの実装

LiveSDK01

こんにちは。共同開発部 開発担当の伊藤です。

今日は Windows ストアアプリについてご紹介します。フェンリルの共同開発部では iOS / Android だけでなく、Windows ストアアプリの開発も行っています。Windows 8.1 もそろそろ広がってきていて、某 Flash ゲームのブームもあって「PC も Windows タブレットも使ってるよ!」という方もぼちぼち出てきているかと思います。

Windows ストアアプリを作るとき、同じユーザーを PC ごとに ID/パスワードでログインさせる方法もありますが、PC がたくさんあるとそれぞれ別々にログインするのは面倒です。また、Windows ストアアプリ専用に作るサービスでわざわざ新規に ID やパスワードを作るのは手間ですし、離脱原因にもなります。

今回は「アプリと Web サイトのシングル サインオン」という方法を使って、同じ Microsoft アカウントでログインしてあれば、1度アプリに対して許可を与えれば全ての PC で一発でログインできるアプリを実装する方法をご紹介します。

アプリと Web サイトのシングルサインオン

Windows ストアアプリの「アプリと Web サイトのシングルサインオン」という機能は、ユーザーが Windows のログインに使用している Microsoft アカウントに紐づけられる ID を使用して、アプリが使用するバックエンドサーバーへのログインを行う方法です。

Microsoft アカウントで Windows にログインしているユーザーは許可ボタンを1度押すだけで、アプリからバックエンドサーバーへのログインを行うことができ、また一度認証しておけば、同じ Microsoft アカウントでログインしている他の Windows では何も聞かれずに最初からサーバーにログインして、サーバーに保存された情報にアクセスできる状態になります。

動作のステップとしては、以下のような動きになります。

  • アプリから Windows に対してログイン状態を確認
    • 既に他の Windows でアプリにログインされていれば、Windows から認証情報が返ってくる
  • アプリからログインを実行
    • ユーザーに対して認可要求ダイアログが表示される
    • ユーザーが認可ボタンを押すと、認証情報が返ってくる
  • アプリは返ってきた認証情報を、サーバーに送る
  • サーバーは認証情報の署名を検証し、正しければログイン状態とする

概要はつかんでいただけたでしょうか。ここから先は実際に実装する方法を詳しくご紹介します。

アプリ側の準備

まず、Windows ストアアプリでシングルサインオンの機能を使うためには、Live Connect サービスをアプリで使えるようにする必要があります。この機能は Windows Store のダッシュボードにアプリの登録を行って、アプリの ID を用意する必要がありますので、事前に費用を支払って開発者アカウントを開設しておく必要があります。

開発者アカウントの用意ができたら、Visual Studio でアプリのプロジェクトを右クリックし、「ストア」メニューの「アプリケーションとストアを関連づける」を選択します。

LiveSDK11

ウィザードを進めていくと、Microsoft アカウントのログイン後、アプリを選択する画面になります。すでにダッシュボードで名前を予約していればそれを選択、まだ名前を作っていない場合は「新しいアプリケーション名を予約」のフィールドに名前を入れて新規のデータを作ってから、関連づけを行います。

LiveSDK12

次に、Live SDK をアプリに導入します。「NuGet パッケージの管理」から Live SDK を検索してインストールします。

LiveSDK13

ダッシュボードの準備

Windows ストア開発者サイトのダッシュボードにあるアプリのページに、サービスという項目がありますので、まずはこのページを開きます。

LiveSDK21

サービスのページ文中にあるリンクから「Live サービス サイト」へ移動します。

LiveSDK22

Live サービスサイト内では、ユーザーの Microsoft アカウント管理画面に表示される情報(名前、アイコン)を管理したり、各種認証情報の取得、設定を行います。

まず、アプリケーション設定の項目にある「クライアント シークレット」をコピーしておいてください。この情報は後ほど、アプリから送られてくる認証トークンの署名の有効性を確認するのに使用します。

LiveSDK23

次に、「API 設定」のタブで、「リダイレクト URL」を入力します。Windows ストアアプリでだけ使用する場合は、自分の持っているWebサイト内のURLを入力しておきます。

LiveSDK24

リダイレクト URL は、アプリ側でも識別のため利用するほか、認証ダイアログに入力した Web サイトのドメインが表示されます。(なお、ブラウザからもログインする場合は OAuth コールバックを受け取る URL が必要と思いますが、今回はそこまで検証していません)

また、基本情報やローカライズ設定は、Microsoft アカウントの管理画面上に表示されるアイコンやアプリ名、プライバシーポリシーの URL などを指定できるので、リリースまでに別途適切に設定しておくとよいでしょう。

アプリの実装

アプリ側では Live SDK の Microsoft.Live.LiveAuthClient クラスを作ります。このとき、引数に上記設定で入力したリダイレクト URL を指定します。

authClient = new LiveAuthClient("<<リダイレクトURL>>");

InitializeAsync メソッドを呼び出すと、すでにユーザーがアプリにログインしたことがあれば、認証情報を渡してくれます。このとき、スコープを指定しますが、アプリがどのような情報を要求するかを指定します。今回はシングルサインオンだけなので「wl.signin」のみですが、ユーザーの名前などを取得したい場合は「wl.basic」も指定すると良いでしょう。(詳細は MSDN のスコープとアクセス許可 (Live Connect) を参照してください)

async void LiveInitialize()
{
    var result = await authClient.InitializeAsync(new[] { "wl.signin" });
    if (result != null && (result.Status == LiveConnectSessionStatus.Connected))
    {
        LoadUserData(result.Session.AuthenticationToken);
    }
}

最後に、ユーザーが最初にログインが必要な機能を使おうとしたときに、LoginAsync メソッドを呼び出します。ログインされていればそのまま結果が返り、ログインされていなければ認可要求ダイアログが表示されます。

async Task<bool> UploadFileAsync(string text)
{
    var result = await authClient.LoginAsync(new[] { "wl.signin" });
    if (result.Status == LiveConnectSessionStatus.Connected)
    {
        // ログインできたからアップロード
        await DoUploadFileAwait(result.Session.AuthenticationToken, text);
    }
}

ここで、上記のメソッドは実際のアップロード処理に AuthenticationToken プロパティを使用しているのがわかるでしょうか。この値が Web サイトに認証情報として渡す JWT(JSON Web Token) となります。

AuthenticationToken は別の設定値に保存して使用しないようにします。AuthenticationToken の有効期限は 24 時間ですが、AuthenticationToken プロパティから取得するときに有効期限が切れていれば自動的に新しいトークンを生成するようになっているからです。

なお、認可ダイアログは以下のような画面が表示されます。

LiveSDK31

また、LiveAuthClient.PropertyChanged イベントを使用すれば、LiveAuthClient の状態が変化したタイミングを検出することができますので、画面への状態反映(右肩に表示されるログインアカウントのアイコン表示など)はこちらを使うと便利です。

サーバーの実装

サーバー側では受け取った JWT をパース・署名検証を行い、これをもって認証します。

ここで注意が必要なのは、クライアントシークレットに対して “JWTSig” という文字列を末尾につけたものが認証キーとなるということです。

デコードされたデータの uid というパラメータがその Microsoft アカウントの ID となります。サービス独自の認証機能を持つアプリの場合は、この uid とサービス側の ID を紐付け、AuthenticationToken での確認が取れれば認証成功とするという方法をとればよいでしょう。

以下に、PHP と Java でのデコードサンプルをご紹介します。C# に関しては Live SDK のソースコードにライブラリがあります

PHPでのサーバーサイド処理例

php-jwt を使用します。

<?php
require ’JWT.php’;

$hs256_token = ”<受信したJWT>”; 
$client_secret = ”<クライアントシークレット>”;

$key = pack('H*', hash('sha256', $shared_key . 'JWTSig'));
try {
  $jwt = JWT::decode($hs256_token, $key);
  if ($jwt->exp >= time()) {
    // ログイン成功
  }
  else {
    // 有効期限切れ
  }
} catch (Exception $e) {
  // 署名検証失敗 
}

Javaでのサーバーサイド処理例

Nimbus-JOSE-JWT を使用します。

/**
* Live SDK が返す JWTを検証し、正しければ uid を返す
* @param authenticationToken 送られてきた AuthenticationToken
* @param clientSecret Live サービスサイトで取得したクライアントシークレット
*/
private static String parseJwtUid(String authenticationToken,
        String clientSecret) {
    MessageDigest sha256 = DigestUtils.getSha256Digest();
    byte[] key = sha256.digest((clientSecret + "JWTSig").getBytes());
    
    try {
        JWT jwt = JWTParser.parse(authenticationToken);
        if (jwt instanceof SignedJWT) {
            JWSVerifier verifier = new MACVerifier(key);
            SignedJWT sjwt = (SignedJWT) jwt;
            if (sjwt.verify(verifier)) {
                ReadOnlyJWTClaimsSet cs = sjwt.getJWTClaimsSet();
                Date now = new Date();
                if (now.before(cs.getExpirationTime())) {
                    // ログイン成功
                    return cs.getStringClaim("uid");
                }
                else {
                    // 有効期限切れ
                }
            }
            else {
                // 署名検証失敗
            }
        }
        else {
            // 対応していないJWT
        }
    } catch (ParseException e) {
        // JWTではない
        e.printStackTrace();
    } catch (JOSEException e) {
        // JWTではない
        e.printStackTrace();
    }
    return null;
}

注意点

NuGet の Live SDK を導入した場合、Live SDK 内でエラーが発生した場合、NullReferenceException の未ハンドル例外が発生してしまいます。エラーメッセージを導出するためのリソースファイルが含まれていないからなのですが、これを解決するためには過去のバージョンの Live SDK をインストールし、インストールされたファイルから Microsoft.Live.pri をプロジェクトにコピーしてきて含めることで解決できます。

Live SDK v5.2をダウンロードしてインストールすると、C:\Program Files (x86)\Microsoft SDKs\Live\v5.0\Metro XAML\Redist\CommonConfiguration\Neutral に .pri ファイルがインストールされますのでこれをプロジェクトの Assets フォルダに追加し、ビルドアクションをコンテンツに設定すれば対策完了です。

最後に

このほかにも、Windows ストアアプリでは同じアカウントでログインしていればどこでも同じように使えるという体験を作るために RoamingSetting という設定を Microsoft のクラウドサービスを使って同期させる機能も用意されています。こちらはデータ量に上限があるので、使いどころをうまく検討する必要があります。

また、Live SDK を使えば、OneDrive へアクセスしてファイルをダウンロード・アップロードすることもできますので、興味のある方はリファレンスをご覧になってはいかがでしょうか。

参考サイト

フェンリルのオフィシャル Twitter アカウントでは、フェンリルプロダクトの最新情報などをつぶやいています。よろしければフォローしてください!

フェンリルの開発者アカウントでは、フェンリルプロダクトの最新開発情報などをつぶやいています。よろしければフォローしてください!

Copyright © 2018 Fenrir Inc. All rights reserved.