読者です 読者をやめる 読者になる 読者になる

かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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:パスワードは平文で保存しちゃだめよ