1. ホーム
  2. c#

[解決済み] OWINセキュリティ - OAuth2 Refreshトークンの実装方法

2023-06-28 16:04:02

質問

Visual Studio 2013に付属するWeb API 2のテンプレートを使っているのですが、OWINミドルウェアでユーザー認証などを行っています。

このテンプレートでは OAuthAuthorizationServerOptions OAuth2 サーバーが 14 日で期限切れになるトークンを配布するように設定されていることに気づきました。

 OAuthOptions = new OAuthAuthorizationServerOptions
 {
      TokenEndpointPath = new PathString("/api/token"),
      Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) ,
      AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
      AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
      AllowInsecureHttp = true
 };

これは、私の最新のプロジェクトには適していません。私は、短命の bearer_token を配布し、その更新は refresh_token

たくさんググってみましたが、参考になるものが見つかりません。

というわけで、なんとかここまでたどり着きました。今、私は "WTF do I now" のポイントに到達しています。

を書きました。 RefreshTokenProvider を実装した IAuthenticationTokenProvider と同じように RefreshTokenProvider プロパティに OAuthAuthorizationServerOptions クラスのプロパティです。

    public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
    {
       private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();

        public async Task CreateAsync(AuthenticationTokenCreateContext context)
        {
            var guid = Guid.NewGuid().ToString();


            _refreshTokens.TryAdd(guid, context.Ticket);

            // hash??
            context.SetToken(guid);
        }

        public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
        {
            AuthenticationTicket ticket;

            if (_refreshTokens.TryRemove(context.Token, out ticket))
            {
                context.SetTicket(ticket);
            }
        }

        public void Create(AuthenticationTokenCreateContext context)
        {
            throw new NotImplementedException();
        }

        public void Receive(AuthenticationTokenReceiveContext context)
        {
            throw new NotImplementedException();
        }
    }

    // Now in my Startup.Auth.cs
    OAuthOptions = new OAuthAuthorizationServerOptions
    {
        TokenEndpointPath = new PathString("/api/token"),
        Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) ,
        AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
        AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(2),
        AllowInsecureHttp = true,
        RefreshTokenProvider = new RefreshTokenProvider() // This is my test
    };

これで、誰かが bearer_token を送信するようになりました。 refresh_token を送信しています。

では、この refresh_token を使って、どのように新しい bearer_token を取得するには、おそらく特定の HTTP ヘッダーを設定してトークン エンドポイントにリクエストを送信する必要があるのではないでしょうか。

入力しながら声に出して考えています... refresh_tokenの有効期限は、次のように処理する必要があります。 SimpleRefreshTokenProvider ? クライアントはどのようにして新しい refresh_token ?

私はこれを間違いたくないし、ある種の標準に従いたいので、いくつかの読み物/ドキュメントがあれば本当に助かります。

どのように解決するのですか?

Bearer(以下access_tokenと呼ぶ)とRefresh Tokenを使ってOWIN Serviceを実装したところです。これに対する私の洞察は、異なるフローを使用することができるということです。つまり、access_tokenとrefresh_tokenの有効期限をどのように設定するかは、使いたいフローに依存するということです。

私は2つの フロー A B を追加してください(Bの流れにすることをお勧めします)。

A) access_tokenとrefresh_tokenの有効期限は、デフォルトで1200秒または20分となっており、同じです。このフローでは、まずクライアントがログインデータと共にclient_idとclient_secretを送信し、access_token、refresh_token、expiration_timeを取得する必要があります。refresh_tokenにより、20分間(またはOAuthAuthorizationServerOptionsのAccessTokenExpireTimeSpanを設定した時間)新しいaccess_tokenを取得することが可能になりました。access_tokenとrefresh_tokenの有効期限は同じなので、クライアントは有効期限が切れる前に新しいaccess_tokenを取得する責任があります!例えば、クライアントはaccess_tokenを送信することができます。例えば、クライアントはあなたのトークンエンドポイントにリフレッシュPOSTコールを送信し、ボディを送ることができます。

grant_type=refresh_token&client_id=xxxxxx&refresh_token=xxxxxxxx-xxxx-xxxx-xxxx-xxxxx

で、トークンの期限切れを防ぐために、例えば19分後に新しいトークンを取得します。

B) このフローでは、access_tokenに短期間の有効期限を、refresh_tokenに長期間の有効期限を設定したいと思います。テスト用に、access_tokenの有効期限を10秒に設定したとします ( AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10) ) とし、refresh_token を 5 分に設定します。さて、ここからが refresh_token の有効期限を設定する興味深い部分です。SimpleRefreshTokenProviderクラスのcreateAsync関数でこのように行います。

var guid = Guid.NewGuid().ToString();


        //copy properties and set the desired lifetime of refresh token
        var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
        {
            IssuedUtc = context.Ticket.Properties.IssuedUtc,
            ExpiresUtc = DateTime.UtcNow.AddMinutes(5) //SET DATETIME to 5 Minutes
            //ExpiresUtc = DateTime.UtcNow.AddMonths(3) 
        };
        /*CREATE A NEW TICKET WITH EXPIRATION TIME OF 5 MINUTES 
         *INCLUDING THE VALUES OF THE CONTEXT TICKET: SO ALL WE 
         *DO HERE IS TO ADD THE PROPERTIES IssuedUtc and 
         *ExpiredUtc to the TICKET*/
        var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);

        //saving the new refreshTokenTicket to a local var of Type ConcurrentDictionary<string,AuthenticationTicket>
        // consider storing only the hash of the handle
        RefreshTokens.TryAdd(guid, refreshTokenTicket);            
        context.SetToken(guid);

これで、クライアントは access_token が期限切れになったとき、クライアントはトークン・エンドポイントに refresh_token を使って POS コールを送ることができます。呼び出しのボディ部分は次のようになります。 grant_type=refresh_token&client_id=xxxxxx&refresh_token=xxxxxxxx-xxxx-xxxx-xxxx-xx

重要なことは、このコードをCreateAsync関数だけでなく、Create関数でも使用したい場合があることです。そのため、上記のコードには独自の関数(例えばCreateTokenInternalという関数)を使用することを検討する必要があります。 ここでは、refresh_tokenフローを含むさまざまなフローの実装を見つけることができます。 (ただし、refresh_tokenの有効期限を設定しない場合)

githubにあるIAuthenticationTokenProviderの実装例を一つ紹介します。 (refresh_tokenの有効期限を設定したもの)

OAuth 仕様書と Microsoft API Documentation 以外の資料でお役に立てず、申し訳ありません。ここにリンクを貼りたいのですが、私のレピュテーションでは2つ以上のリンクを貼ることができません......。

私は、access_tokenの有効期限と異なるrefresh_tokenの有効期限を持つOAuth2.0を実装しようとするときに、これが他の人の時間の節約になることを願っています。私はウェブ上で実装例を見つけることができず(上記のリンク先のthinktectureのものを除く)、それが私のために動作するまで何時間も調査しました。

新しい情報です。私の場合、トークンを受け取るために 2 つの異なる可能性があります。1つは、有効なaccess_tokenを受信することです。この場合、次のデータを含む application/x-www-form-urlencoded 形式の文字列ボディを持つ POST コールを送信する必要があります。

client_id=YOURCLIENTID&grant_type=password&username=YOURUSERNAME&password=YOURPASSWORD

次に、access_token が有効でなくなった場合、refresh_token を試すには、以下の形式の文字列を本文に含む POST コールを送信します。 application/x-www-form-urlencoded という形式の文字列のボディに、以下のデータを記述してPOSTを送信します。 grant_type=refresh_token&client_id=YOURCLIENTID&refresh_token=YOURREFRESHTOKENGUID