WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

ASP.NET Web API の認証

by WebSurfer 2018年12月17日 15:40

Visual Studio 2013 / 2015 の Web API テンプレートで、認証を「個別のユーザーアカウントカウント」として自動生成させた Web API アプリの認証は、デフォルトで、トークンを要求ヘッダに含めてサーバーに送信するトークンベースになります。(MVC テンプレートは不明。Core は全く不明)

ベアラトークン

上の画像は Web API に要求をかけたときの要求ヘッダを Fiddler で表示したものです。赤枠で示した部分が認証に使われるトークンです。(画像には認証クッキーも含まれていますが、その理由等については後述します)

(注:クッキーではなくトークンを使う理由はセキュリティ (CSRF) 対策だそうです。MVC アプリではビューに Html ヘルパーの AntiForgeryToken メソッドを、アクションメソッドに [ValidateAntiForgeryToken] 属性を付与して CSRF 対策ができますが、Web API ではそういう手段が使えませんから)

テンプレートで自動生成されたそのままの状態でアプリを実行してトークンが送られるわけではないです。トークンを送って認証が通るようにする手順と注意事項を以下に書いておきます。

トークンによる認証の仕組みについては Microsoft の文書 Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2 に詳しく書いてありますので、それを読むことをお勧めします。

その記事からリンクが張ってある GitHub のサイトから完全なサンプルが入手できます。それを試してもらってもいいのですが、そのサンプルには SSL 関係や knockout.js など Web API の認証とは直接関係のない部分もあって分かり難いところがあるかもしれません。(注:運用上は SSL の実装は必須です。knockout.js は必須ではありませんが)

なので、以下に Visual Studio 2015 の Web API テンプレート作成したアプリに、追加で何を実装すればトークンを入手でき、そしてトークンをどのように送信すれば認証が通るかだけを書いておきます。

事前に、ASP.NET Identity がユーザー情報を取得する LocalDB に email と password を登録し有効なユーザーアカウントを作成しておく必要があります。(その手順は本題とは直接関係ないので割愛します。手抜きでスミマセン)

トークンの入手は、ビューに以下のようなスクリプトを追加し、jQuery ajax を使って登録済みの email と password をトークンエンドポイント /Token に POST してやります。email と password が有効であれば、応答の JSON 文字列にトークンが含まれて戻ってきます。そのトークンを sessionStorage に保存します。

var tokenKey = 'accessToken';

function getToken() {
    var email = document.getElementById("email").value;
    var password = document.getElementById("password").value;

    var loginData = {
        grant_type: 'password',
        username: email,
        password: password
    };

    $.ajax({
        type: "POST",
        url: "/Token",
        data: loginData,
        success: function (data) {
          sessionStorage.setItem(tokenKey, data.access_token);
        },
        error: function (jqXHR, textStatus, errorThrown) {
            // ・・・中略・・・
        }
    });
}

トークンの送信は、上の getToken メソッドで sessionStorage に保存したトークンを取得し、以下のように要求ヘッダの Authorization に設定して送信してやります。それで認証が通って期待した応答が返ってきます。

function apiHeroesGet() {
    var token = sessionStorage.getItem(tokenKey);
    var headers = {};
    if (token) {
        headers.Authorization = 'Bearer ' + token;
    }

    $.ajax({
        type: "GET",
        url: "/api/values",
        headers: headers,
        success: function (data, textStatus, jqXHR) {
            // ・・・中略・・・
        },
        error: function (jqXHR, textStatus, errorThrown) {
            // ・・・中略・・・
        }
    });
}

主な話は以上ですが、追加で 2, 3 気になったことを書いておきます。

(1) トークンエンドポイント /Token

上のスクリプトの getToken メソッドの email と password の送信先 /Token は、Startup.Auth.cs の ConfigureAuth メソッドで OAuthAuthorizationServerOptions クラスを初期化するときに TokenEndpointPath プロパティに設定されます。

自動生成されたコードの中のアクションメソッドだと思って探したけれど、見つからないので焦ったというのは内緒です。(笑)

(2) 認証クッキーの発行

上の画像で認証クッキーも発行されていますが、それは上のスクリプトの getToken メソッドでトークンを発行する際に同時に発行されます。

デフォルトで、Startup.Auth.cs の ConfigureAuth メソッドには app.UseCookieAuthentication(...) メソッドが含まれていることに注意してください。

MVC 側のアクションメソッドの認証には、この認証クッキーが使用されます。例えば MVC 側のアクションメソッドに [Authorize] を付与してやると、認証クッキーなしでは 401 応答が返ってきます。

401 応答ではなく、302 応答を返してログインページにリダイレクトするには、引数の CookieAuthenticationOptions オブジェクトの LoginPath プロパティにログインページの URL を new PathString("/Account/Login") というように指定してやります。

(3) AuthenticationType の設定

UseCookieAuthentication メソッドの引数 CookieAuthenticationOptions オブジェクトの AuthenticationType を設定すると、トークンエンドポイント /Token に email と password を送信しても認証クッキーは発行されなくなります。(なお、トークンは発行されます)

認証クッキーは MVC 側でのログインだけで発行したい場合は、AuthenticationType を設定すればよさそうです。そのことが書いてあるドキュメントは見つけられなかったのですが、そのような目的のためにそうなっているような気がします。

AuthenticationType に設定されるのは単なる文字列で、何かの識別用に使っているらしいです。これをきちんと指定しないとログアウトできないなどの問題が出るという stackoverflow の記事を見かけました。(自分が Web API テンプレートのアプリで試した限りではそういう問題は出なかったですが・・・)

(4) トークンによる認証の強制

デフォルトでは Web API 側の認証はトークンになるのは、WebApiConfig.cs にある以下のコードによります。

config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(
                OAuthDefaults.AuthenticationType));

前者のメソッドでホスト(IIS と MVC) による認証は無視され、Web API には匿名アクセスとなるらしいです。後者のメソッドで HostAuthenticationFilter によりベアラトークンによる認証が行われるようになるそうです。

ちなみに、上の 2 行をコメントアウトすると認証クッキーだけで認証が通ってしまうようになります。

詳しくは、Microsoft の文書 Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2 の Configuring Web API to use Bearer Tokens のセクションに詳しく書いてありますので、そちらを見てください。

Tags: ,

MVC

MVC でファイルのダウンロード

by WebSurfer 2018年12月12日 16:06

ASP.NET MVC アプリケ��ションでファイルをダウンロードするのはどうすればいいかということを備忘録として書いておきます。(Web Forms アプリの場合は先の記事「ダウンロードは HTTP ハンドラで」を見てください)

IE11 の通知バー

上の画像のように通知バーを表示し(IE11 の場合です)、ファイル名とファイルの種類をブラウザにきちんと認識させるのが条件です。

基本的には以下のアクションメソッド FileDownload のようにします。

public ActionResult FileDownload()
{
    string fileName = Server.MapPath("~/Files/test.pdf");
    return File(fileName, "application/pdf", "test.pdf");
}

上のコードの return File(...); のヘルパーメソッド File は、FileResult クラスを継承した FilePathResult, FileContentResult, FileStreamResult 派生メソッドのいずれかを呼び出します。

それらの使い分けは以下の通りです。

  1. FilePathResult: コンテンツが既存のファイルとして提供される。
  2. FileStreamResult: コンテンツがストリームとして提供される。
  3. FileContentResult: コンテンツがバイト配列として提供される。

上記のどれを呼び出すかは return File(...); の引数によります。上の記事の例では第一引数が文字列(ファイルの物理パス)なので FilePathResult が呼び出されます。

FilePathResult は Web Forms アプリでもファイルをダウンロードする際に使われている TransmitFile メソッドを呼び出します。TransmitFile メソッドは、FilePathResult の第 1 引数に指定されたファイル名を受けて、そのファイルをメモリにバッファリングせずに、HTTP 応答出力ストリームに直接書き込みます。

FileStreamResult は、第 1 引数に指定されるストリームから FileStream.Read メソッドでバイト列を取得し、それを HttpReponse.OutputStream.Write メソッドを使って出力ストリームに書き出すという操作を行います。

FileContentResult は、第 1 引数に指定されるバイト列を直接 HttpReponse.OutputStream.Write メソッドを使って出力ストリームに書き出すという操作を行います。

ファイル名とファイルの種類をブラウザにきちんと認識させ、上の画像のような通知バーを出させるには、第 2, 3 引数に順にファイルの MIME タイプ、拡張子を含むファイル名を設定します。そうすることにより、応答ヘッダに Content-Type と Content-Disposition が適切に設定されます。

なお、第 2 引数に設定するファイル名には US-ASCII 文字を使用しないと IE では文字化けするので注意してください。先の記事「ダウンロードファイル名の文字化け」に書いた方法でどのように対応できるかは未調査です。

キャッシュコントロールは Web Forms アプリと同様に HttpResponse.Cache で取得できる HttpCachePolicy オブジェクトを使って可能です。

例えば、上の FileDownload アクションメソッドに以下のコードを追加してやれば、

Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetExpires(DateTime.Now.ToUniversalTime());
Response.Cache.SetMaxAge(new TimeSpan(0, 0, 0, 0));

以下の画像の応答ヘッダのように Cache-Control: no-cache, Pragma: no-cache, Expires: -1 が設定されます。

応答ヘッダ

File メソッドの第 2, 3 引数に設定した MIME タイプとファイル名により、Content-Type と Content-Disposition が適切に設定されているところにも注目してください。

Tags:

Upload Download

ブラウザ画面でセンタリング

by WebSurfer 2018年11月1日 12:10

CSS のみを使ってブラウザ画面上でコンテンツを上下左右センタリングする方法を、あまり需要はないとは思いますが、せっかく考えたので備忘録として書いておきます。

センタリング表示

上の画像は、width / height を 500px / 300px に設定した div 要素を、CSS の position プロパティを使って、ブラウザ画面の中央に来るようにしたものです。ブラウザの画面のサイズを変えても自動的に中央に来るようになっています。

CSS の設定は下にアップしたコードにありますので見てください。position を absolute とし、top / left を 50%(即ち画面の中央)に指定、margin に div 要素のサイズの半分をマイナス値で設定することにより div 要素が画面の中央に来るようにオフセットしています。

position プロパティの詳しい説明&デモは MDN web docs の記事「position」にありますので見てください。要点を抜粋すると以下の通りです。

An absolutely positioned element is an element whose computed position value is absolute or fixed. The top, right, bottom, and left properties specify offsets from the edges of the element's containing block. If the element has margins, they are added to the offset.

上の画像を表示したコードを以下にアップしておきます。ASP.NET Web Forms アプリの .aspx ページを使っていますが、ASP.NET は今回の記事には直接の関係はありません。(.html ページを使うとブラウザのキャッシュをいちいち消さなければならないのが面倒なので .aspx ページを使いました)

<%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="0049-Centering.aspx.cs" 
    Inherits="_0049_Centering" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Centering</title>
    <style type="text/css">
        body {
            background-color: #555;
        }

        .content {
            background-color: white;
            width:500px;
            height:300px;
            position:absolute;
            top:50%;
            left:50%;
            margin-left:-250px;
            margin-top:-150px;
            overflow:scroll;
        }
    </style>
</head>
<body>
  <form id="form1" runat="server">
    <div class="content">
      <h1>
        This page is horizontally / vertically centered on 
        screen that is wider than 500 / 300 pixels.
      </h1>
      <h2>
        Resize the browser window to see the effect.
      </h2>
      <p>
        An absolutely positioned element is an element 
        whose computed position value is absolute or fixed. 
        The top, right, bottom, and left properties specify 
        offsets from the edges of the element's containing 
        block. (The containing block is the ancestor 
        relative to which the element is positioned.) 
        If the element has margins, they are added to the 
        offset.
      </p>
    </div>
  </form>
</body>
</html>

Tags:

ASP.NET

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  2019年2月  >>
272829303112
3456789
10111213141516
17181920212223
242526272812
3456789

View posts in large calendar