web-dev-qa-db-ja.com

Web API / OWIN、SignalR&Authorization

VS 2013の新しいプロジェクトの開始点として、AngularJS、Web API、SignalRアプリケーションのプロトタイプを開発しています。

この段階では、ビジュアルスタジオが個人ユーザーアカウント用に生成する既定のコードをほとんど使用しています。

StartUp.Auth.csコードに次のような行があります。

app.UseOAuthBearerTokens(OAuthOptions);

これで、[Authorize]属性をコントローラーに追加でき、正常に動作します。

ちなみに、angularを使用すると、次のようにJavaScriptでトークンを含む標準ヘッダーを追加できました。

$http.defaults.headers.common.Authorization = 'bearer ' + access_token;

次に、SignalRをプロジェクトに追加しました。
独自のバージョンの[Authorize]属性をサポートしますが、SignalRを使用するときにカスタムヘッダーを渡す方法はありません。
これはブラウザ側の制限です。
ドキュメントでは、クエリ文字列の一部としてトークンを渡すことができると述べています。
JavaScript側にそのコードを追加しました。私のSignalRコードにはこれが含まれています。
トークンを「bearer_token」として渡しました。

this.connection = $.hubConnection("/TestHub", { useDefaultPath: false, qs: "bearer_token=" + token });

したがって、私の問題はOWINにトークンを認識させる方法でしたが、トークンはヘッダーに存在しなくなりました。
何度か検索した後、トークンをクエリ文字列からヘッダーに移動するコードを追加することになりました。
プロトタイプの場合、StartUp.Auth.csの元の行の上に小さなコードを追加しました。

だから、今はこのように見えました:

app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
    Provider = new OAuthBearerAuthenticationProvider()
    {
        OnRequestToken = context =>
        {
            if (context.Request.Path.Value.StartsWith("/TestHub"))
            {
                string bearerToken = context.Request.Query.Get("bearer_token");
                if (bearerToken != null)
                {
                    string[] authorization = new string[] { "bearer " + bearerToken };
                    context.Request.Headers.Add("Authorization", authorization);
                }
            }

            return Task.FromResult(context);
        }
    }
});


// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);

上記のコードは大まかなものですが、これはプロトタイプなので、実際に機能するかどうかを確認したいだけです。

最後に質問に到達します:これは、ベアラートークン認証をSignalRおよびOWINパイプラインと統合するための正しいパターンですか?.
これを行うための正しい方法について多くの良い情報を見つけることができなかったようです。

28
Peter Trenery

私はこのようなクラスを使用します:

public class OAuthTokenProvider : OAuthBearerAuthenticationProvider
{
    private List<Func<IOwinRequest, string>> _locations;
    private readonly Regex _bearerRegex = new Regex("((B|b)earer\\s)");
    private const string AuthHeader = "Authorization";

    /// <summary>
    /// By Default the Token will be searched for on the "Authorization" header.
    /// <para> pass additional getters that might return a token string</para>
    /// </summary>
    /// <param name="locations"></param>
    public OAuthTokenProvider(params Func<IOwinRequest, string>[] locations)
    {
        _locations = locations.ToList();
        //Header is used by default
        _locations.Add(x => x.Headers.Get(AuthHeader));
    }

    public override Task RequestToken(OAuthRequestTokenContext context)
    {
        var getter = _locations.FirstOrDefault(x => !String.IsNullOrWhiteSpace(x(context.Request)));
        if (getter != null)
        {
            var tokenStr = getter(context.Request);
            context.Token = _bearerRegex.Replace(tokenStr, "").Trim();
        }
        return Task.FromResult<object>(null);
    }
}

トークンをヘッダーに渡すだけでなく、それを解析してコンテキストに設定します。

その後、次のようにアプリ構成で使用できます。

app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
    Provider = new OAuthTokenProvider(
         req => req.Query.Get("bearer_token"),
         req => req.Query.Get("access_token"),
         req => req.Query.Get("token"),
         req => req.Headers.Get("X-Token"))    
});

次に、次のスタイルのリクエストでは、認証と承認で使用するために、トークンが暗号化されません。

GET https://www.myapp.com/authorized/endpoint?bearer_token=123ABC HTTP/1.1
GET https://www.myapp.com/authorized/endpoint?access_token=123ABC HTTP/1.1
GET https://www.myapp.com/authorized/endpoint?token=123ABC HTTP/1.1

GET https://www.myapp.com/authorized/endpoint HTTP/1.1
X-Token: 123ABC

GET https://www.myapp.com/authorized/endpoint HTTP/1.1
Authorization: 123ABC
19
calebboyd

これは私がそれをどのように解決したか

var authData = localStorageService.get('authorizationData');
var token = authData.token;
 $.signalR.ajaxDefaults.headers = { Authorization: "Bearer " + token };

サーバー側のコードに変更はありません

それが誰かを助けることを願って

16

私はこれを将来この問題が発生する人々のために投稿します:

これを機能させるには、アプリケーションの目的に応じて、複数の方法があります。

  1. 最初に目にしたのはSignalRがヘッダーで機能するようにすることで、実装は非常に簡単に見えます。

    $.signalR.ajaxDefaults.headers = { Authorization: "Bearer " + token };

これの大きな欠点は、SignalRを使用しているため、SignalRlongPollingの使用を強制することです。

  1. 2番目のアプローチは、接続の直前にクライアントがaccess tokenとしてログインするときに受け取るquery stringを渡すことです。次に、Attributeを継承するカスタムAuthorizeAttributeを作成します( このリポジトリに従ってください-私の意見ではあまりよくありませんが、ポイントになります )。

トークンをquery stringとして渡す別のアプローチは これに従いますSO answer これはOAuth Providerを作成し、

パイプラインの早い段階でトークンから他のすべての値を取得します

繰り返しますが、query stringsは非常に脆弱であるため、この方法は最適ではありません。

クエリ文字列は、Webサーバーのログに保存される(またはSSLを使用している場合でも他の方法で公開される)傾向があります。誰かがトークンを傍受するリスクがあります。シナリオに最適なアプローチを選択する必要があります。

  1. 私が現在実装しようとしているソリューション(および機能するようになると更新が返されます:D)は このブログ投稿に基づくOAuth Bearer Token authenticationSignalRとともに使用しますトークンをCookie経由でSignalRパイプラインに渡します。

これは妥協の少ないソリューションだと思いますが、実装が完了したら、さらに多くの情報を提供します。

お役に立てれば。がんばって!

更新トークンをCookieに格納してプロバイダーで取得することにより、WebApiトークン認証をSignalRで機能させることができました。

あなたは このGitHubリポジトリを見てください を取ることができますが、私は主に上記の記事をフォローしました。明らかに、これが私がしたことです:

OAuthBearerTokenAuthenticationProviderを継承するOAuthBearerAuthenticationProviderクラスを作成し、RequestTokenメソッドをオーバーライドしました。

次に、BearerTokenが格納されているCookieを探し、その値を取得します。次に、context.TokenをCookieの値に設定します。

次に、JavaScriptの部分で(ユーザー名とパスワードを使用して認証することにより)トークンを取得し、Cookieにトークンを格納する必要があります。

public class OAuthBearerTokenAuthenticationProvider : OAuthBearerAuthenticationProvider
{
    public override Task RequestToken(OAuthRequestTokenContext context)
    {
        var tokenCookie = context.OwinContext.Request.Cookies["BearerToken"];

        if (!String.IsNullOrEmpty(tokenCookie))
            context.Token = tokenCookie;

        return Task.FromResult<object>(null);
    }
}

実用的なサンプルについては、上記のリポジトリをご覧ください。

がんばって!

14
radu-matei