かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Web API + Form認証

メンバーシッププロバイダーとか実装したくないですはい。ということで認証かけてないWebAPIを1個置いといて、その中で認証チケットの発行という感じかなあ。

Web.configでフォーム認証を有効かするためにsystem.webの下に以下の要素を追加

<authentication mode="Forms" />

こんな感じのクラスをクラスライブラリに定義しておいて…(ポータブルライブラリがいいかな)

using System.Runtime.Serialization;

namespace AuthApiTest.Models
{
    [DataContract]
    public class LoginParameter
    {
        [DataMember(Name = "loginId")]
        public string LoginId { get; set; }
        [DataMember(Name = "password")]
        public string Password { get; set; }
    }
}
using System.Runtime.Serialization;

namespace AuthApiTest.Models
{
    [DataContract]
    public class LoginResult
    {
        [DataMember(Name = "userName")]
        public string UserName { get; set; }
    }
}

こんな感じのWebAPIをおいとく。*1

using AuthApiTest.Models;
using System.Net;
using System.Web.Http;
using System.Web.Security;

namespace AuthApiTest.Controllers
{
    public class AuthController : ApiController
    {
        public LoginResult Post(LoginParameter param)
        {
            if (param.LoginId == "user" && param.Password == "p@ssw0rd")
            {
                FormsAuthentication.SetAuthCookie("user", false);
                return new LoginResult { UserName = "田中" };
            }

            // こういうとき、何番返すのが妥当なんだろうか
            throw new HttpResponseException(HttpStatusCode.BadRequest);
        }
    }
}

認証かかったAPIとしてValuesControllerというのも作っておきます。

using System.Collections.Generic;
using System.Linq;
using System.Web.Http;

namespace AuthApiTest.Controllers
{
    // 認証しないと呼べないよ~
    [Authorize]
    public class ValuesController : ApiController
    {
        public IEnumerable<string> Get()
        {
            return Enumerable.Range(1, 10).Select(i => "value" + i);
        }
    }
}


お膳立てはできたのでクライアント側です。今回はコンソールアプリケーションでいってみます。コンソールアプリケーションには、LoginParameterやLoginResultクラスを定義したライブラリを追加しておきます。あとは、HttpClientの最新のやつをNuGetからいれておきます。PostAsAsyncとかのAPIがあるとすごい楽なので。

  • Microsoft.AspNet.WebApi.Client

あとは、ログインして呼び出してみる。こんな感じに書いてみました。ストアアプリもHttpClientのリリース前のバージョンだと、PostAsJsonAsyncとかあって便利。早く正式こないかなぁ。

using AuthApiTest.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;

namespace AuthApiTest.Client
{
    class Program
    {
        static void Main(string[] args)
        {
            var cookie = new CookieContainer();
            Console.WriteLine("-------");
            {
                // 認証して通信
                var client = Create(cookie);
                var response = client.PostAsJsonAsync("http://localhost:3502/api/auth",
                    new LoginParameter
                    {
                        LoginId = "user",
                        Password = "p@ssw0rd"
                    }).Result;
                Console.WriteLine(response.Content.ReadAsAsync<LoginResult>().Result.UserName);
                var response2 = client.GetAsync("http://localhost:3502/api/values").Result;
                // 失敗してたら例外とばす
                response2.EnsureSuccessStatusCode();

                Console.WriteLine(response2.Content
                    .ReadAsAsync<IEnumerable<string>>()
                    .Result
                    .Aggregate((c, a) => c + ", " + a));
            }
            Console.WriteLine("-------");
            {
                // 認証してないと失敗する確認
                var client = Create(new CookieContainer());
                var response = client.GetAsync("http://localhost:3502/api/values").Result;
                try
                {
                    // 失敗してたら例外とばす
                    response.EnsureSuccessStatusCode();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }

            }
            Console.WriteLine("-------");
            {
                // cookie containerさえ共有してればおk
                // 認証して通信
                var client = Create(cookie);
                var response = client.GetAsync("http://localhost:3502/api/values").Result;
                // 失敗してたら例外とばす
                response.EnsureSuccessStatusCode();

                Console.WriteLine(response.Content
                    .ReadAsAsync<IEnumerable<string>>()
                    .Result
                    .Aggregate((c, a) => c + ", " + a));
            }
        }

        // HttpClient作るよ
        private static HttpClient Create(CookieContainer cookie)
        {
            return new HttpClient(new HttpClientHandler { CookieContainer = cookie });
        }
    }
}

実行結果は以下のようになります。

-------
田中
value1, value2, value3, value4, value5, value6, value7, value8, value9, value10
-------
Response status code does not indicate success: 401 (Unauthorized).
-------
value1, value2, value3, value4, value5, value6, value7, value8, value9, value10

*1:パスワードは平文で保存しちゃだめよ