public async Task <ActionResult> TestClientCredentialsFlow() { // Tokenエンドポイントにアクセス string aud = ASPNETIdentityConfig.OAuth2AuthorizationServerEndpointsRootURI + ASPNETIdentityConfig.OAuth2BearerTokenEndpoint; // ClientNameから、client_id, client_secretを取得。 string client_id = ""; string client_secret = ""; if (User.Identity.IsAuthenticated) { // User Accountの場合、 client_id = OAuth2Helper.GetInstance().GetClientIdByName(User.Identity.Name); client_secret = OAuth2Helper.GetInstance().GetClientSecret(client_id); } else { // Client Accountの場合、 client_id = OAuth2Helper.GetInstance().GetClientIdByName("TestClient"); client_secret = OAuth2Helper.GetInstance().GetClientSecret(client_id); } string response = await OAuth2Helper.GetInstance() .ClientCredentialsGrantAsync(new Uri( ASPNETIdentityConfig.OAuth2AuthorizationServerEndpointsRootURI + ASPNETIdentityConfig.OAuth2BearerTokenEndpoint), client_id, client_secret, ASPNETIdentityConst.StandardScopes); ViewBag.Response = response; ViewBag.AccessToken = ((JObject)JsonConvert.DeserializeObject(response))[OAuth2AndOIDCConst.AccessToken]; return(View("OAuth2ClientAuthenticationFlow")); }
/// <summary>初期化</summary> private void Init() { this.OAuthAuthorizeEndpoint = ASPNETIdentityConfig.OAuth2AuthorizationServerEndpointsRootURI + ASPNETIdentityConfig.OAuth2AuthorizeEndpoint; this.ClientId = OAuth2Helper.GetInstance().GetClientIdByName("TestClient"); this.State = GetPassword.Generate(10, 0); // 記号は入れない。 this.Nonce = GetPassword.Generate(20, 0); // 記号は入れない。 this.CodeVerifier = ""; this.CodeChallenge = ""; }
public async Task <ActionResult> TestJWTBearerTokenFlow() { // Token2エンドポイントにアクセス string aud = ASPNETIdentityConfig.OAuth2AuthorizationServerEndpointsRootURI + ASPNETIdentityConfig.OAuth2BearerTokenEndpoint2; // ClientNameから、client_id(iss)を取得。 string iss = ""; if (User.Identity.IsAuthenticated) { // User Accountの場合、 iss = OAuth2Helper.GetInstance().GetClientIdByName(User.Identity.Name); } else { // Client Accountの場合、 iss = OAuth2Helper.GetInstance().GetClientIdByName("TestClient"); } // テストなので秘密鍵は共通とする。 string privateKey = OAuth2AndOIDCParams.OAuth2JwtAssertionPrivatekey; privateKey = CustomEncode.ByteToString(CustomEncode.FromBase64String(privateKey), CustomEncode.us_ascii); string response = await OAuth2Helper.GetInstance() .JwtBearerTokenFlowAsync(new Uri( ASPNETIdentityConfig.OAuth2AuthorizationServerEndpointsRootURI + ASPNETIdentityConfig.OAuth2BearerTokenEndpoint2), JwtAssertion.CreateJwtBearerTokenFlowAssertion( iss, aud, new TimeSpan(0, 0, 30), ASPNETIdentityConst.StandardScopes, privateKey)); ViewBag.Response = response; ViewBag.AccessToken = ((JObject)JsonConvert.DeserializeObject(response))[OAuth2AndOIDCConst.AccessToken]; return(View("OAuth2ClientAuthenticationFlow")); }
public async Task <Dictionary <string, object> > GetUserClaims() { // Claim情報を参照する。 // iss, aud, expのチェックは、AccessTokenFormatJwt.Unprotectで実施済。 ClaimsIdentity id = (ClaimsIdentity)User.Identity; Claim claim_aud = id.FindFirst(OAuth2AndOIDCConst.Claim_Audience); // ユーザ認証を行なう。 ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); string subject = ""; if (user == null) { // Client認証 subject = OAuth2Helper.GetInstance().GetClientName(claim_aud.Value); } else { // Resource Owner認証 subject = user.UserName; } Dictionary <string, object> userinfoClaimSet = new Dictionary <string, object>(); userinfoClaimSet.Add(OAuth2AndOIDCConst.sub, subject); // Scope IEnumerable <Claim> claimScope = id.FindAll(OAuth2AndOIDCConst.Claim_Scopes).AsEnumerable(); // scope値によって、返す値を変更する。 foreach (Claim scope in claimScope) { if (user != null) { // user == null では NG な Resource(Resource Owner の Resource) switch (scope.Value.ToLower()) { #region OpenID Connect case OAuth2AndOIDCConst.Scope_Profile: // ・・・ break; case OAuth2AndOIDCConst.Scope_Email: userinfoClaimSet.Add(OAuth2AndOIDCConst.Scope_Email, user.Email); userinfoClaimSet.Add(OAuth2AndOIDCConst.email_verified, user.EmailConfirmed.ToString()); break; case OAuth2AndOIDCConst.Scope_Phone: userinfoClaimSet.Add(OAuth2AndOIDCConst.phone_number, user.PhoneNumber); userinfoClaimSet.Add(OAuth2AndOIDCConst.phone_number_verified, user.PhoneNumberConfirmed.ToString()); break; case OAuth2AndOIDCConst.Scope_Address: // ・・・ break; #endregion #region Else case OAuth2AndOIDCConst.Scope_UserID: userinfoClaimSet.Add(OAuth2AndOIDCConst.Scope_UserID, user.Id); break; case OAuth2AndOIDCConst.Scope_Roles: userinfoClaimSet.Add( OAuth2AndOIDCConst.Scope_Roles, await UserManager.GetRolesAsync(user.Id)); break; #endregion } } else { // user == null でも OK な Resource } } return(userinfoClaimSet); }
/// <summary> /// Authorization Code、Implicitグラント種別において、 /// AuthorizeEndpointPathを処理する場合に発生する。 /// 以下の両方の要素を検証する処理を実装するためのメソッド。 /// ・context.ClientId が、登録された "client_id" であること。 /// ・context.RedirectUri が、そのクライアント用に登録された "redirect_uri" であること。 /// </summary> /// <param name="context">OAuthValidateClientRedirectUriContext</param> /// <returns>Task</returns> /// <see cref="https://msdn.microsoft.com/ja-jp/library/dn385496.aspx"/> public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context) { // context.Validatedに事前登録したRedirectエンドポイントを指定して呼び出し、contextを検証完了に設定する。 // ・ 検証完了にしなければ要求はそれ以上先には進まない。 // ・ RFC上の記載で、RedirectEndpointのURIは、AbsoluteUriである必要があるとの記載あり。 // ASP.NET IdentityのチェックでAbsoluteUriである必要があるとの記載あり形式でないと弾かれる。 #region response_type string response_type = context.Request.Query.Get("response_type"); // OIDC Implicit, Hybridの場合、書き換え if (ASPNETIdentityConfig.EnableOpenIDConnect) { if (response_type.ToLower() == OAuth2AndOIDCConst.OidcImplicit1_ResponseType || response_type.ToLower() == OAuth2AndOIDCConst.OidcImplicit2_ResponseType || response_type.ToLower() == OAuth2AndOIDCConst.OidcHybrid2_IdToken_ResponseType || response_type.ToLower() == OAuth2AndOIDCConst.OidcHybrid2_Token_ResponseType || response_type.ToLower() == OAuth2AndOIDCConst.OidcHybrid3_ResponseType) { // OIDC Implicit, Hybridの場合、書き換え // Authorization Code Flowの場合は、codeなので書き換え不要。 // ※ この変数は、使用するredirect_uriを決定するだめダケに利用される。 response_type = OAuth2AndOIDCConst.ImplicitResponseType; // OIDC Implicit Flow、Hybrid Flowのパラメタチェック // nonceパラメタ 必須 string nonce = context.Request.Query.Get(OAuth2AndOIDCConst.nonce); if (string.IsNullOrEmpty(nonce)) { //throw new NotSupportedException("there was no nonce in query."); context.SetError( "server_error", "there was no nonce in query."); return(Task.FromResult(0)); } // scopeパラメタ 必須 string scope = context.Request.Query.Get(OAuth2AndOIDCConst.scope); if (scope.IndexOf(OAuth2AndOIDCConst.Scope_Openid) == -1) { //throw new NotSupportedException("there was no openid in scope of query."); context.SetError( "server_error", "there was no openid in scope of query."); return(Task.FromResult(0)); } } } if (!string.IsNullOrEmpty(response_type)) { if (response_type.ToLower() == OAuth2AndOIDCConst.AuthorizationCodeResponseType) { if (!ASPNETIdentityConfig.EnableAuthorizationCodeGrantType) { //throw new NotSupportedException(Resources.ApplicationOAuthBearerTokenProvider.EnableAuthorizationCodeGrantType); context.SetError( "server_error", Resources.ApplicationOAuthBearerTokenProvider.EnableAuthorizationCodeGrantType); return(Task.FromResult(0)); } } else if (response_type.ToLower() == OAuth2AndOIDCConst.ImplicitResponseType) { if (!ASPNETIdentityConfig.EnableImplicitGrantType) { //throw new NotSupportedException(Resources.ApplicationOAuthBearerTokenProvider.EnableImplicitGrantType); context.SetError( "server_error", Resources.ApplicationOAuthBearerTokenProvider.EnableImplicitGrantType); return(Task.FromResult(0)); } } } #endregion #region redirect_uri string redirect_uri = context.RedirectUri; // redirect_uriのチェック if (string.IsNullOrEmpty(redirect_uri)) { // redirect_uriの指定が無い。 // クライアント識別子に対応する事前登録したredirect_uriを取得する。 redirect_uri = OAuth2Helper.GetInstance().GetClientsRedirectUri(context.ClientId, response_type); if (!string.IsNullOrEmpty(redirect_uri)) { // 事前登録されている。 if (redirect_uri.ToLower() == "test_self_code") { // Authorization Codeグラント種別のテスト用のセルフRedirectエンドポイント context.Validated( ASPNETIdentityConfig.OAuth2ClientEndpointsRootURI + ASPNETIdentityConfig.OAuth2AuthorizationCodeGrantClient_Account); } else if (redirect_uri.ToLower() == "test_self_token") { // Implicitグラント種別のテスト用のセルフRedirectエンドポイント context.Validated( ASPNETIdentityConfig.OAuth2ClientEndpointsRootURI + ASPNETIdentityConfig.OAuth2ImplicitGrantClient_Account); } else if (redirect_uri.ToLower() == "id_federation_code") { // ID連携時のエンドポイント context.Validated(ASPNETIdentityConfig.IdFederationRedirectEndPoint); } else { // 事前登録した、redirect_uriをそのまま使用する。 context.Validated(redirect_uri); } } else { // 事前登録されていない。 } } else { // redirect_uriの指定が有る。 // 指定されたredirect_uriを使用する場合は、チェックが必要になる。 if ( // self_code : Authorization Codeグラント種別 redirect_uri == (ASPNETIdentityConfig.OAuth2ClientEndpointsRootURI + ASPNETIdentityConfig.OAuth2AuthorizationCodeGrantClient_Manage) ) { // 不特定多数のクライアント識別子に許可されたredirect_uri context.Validated(redirect_uri); } else { // クライアント識別子に対応する事前登録したredirect_uriに string preRegisteredUri = OAuth2Helper.GetInstance().GetClientsRedirectUri(context.ClientId, response_type); //if (redirect_uri.StartsWith(preRegisteredUri)) if (redirect_uri == preRegisteredUri) { // 完全一致する場合。 context.Validated(redirect_uri); } else { // 完全一致しない場合。 context.SetError( "server_error", Resources.ApplicationOAuthBearerTokenProvider.Invalid_redirect_uri); } } } #endregion // 結果を返す。 return(Task.FromResult(0)); }
/// <summary> /// Client Credentialsグラント種別のカスタム認証ロジック /// TokenEndpointPathへの grant_type=client_credentials アクセスは、こちらに到達する。 /// ・client_id, client_secret の検証は、(2) ValidateClientAuthenticationで済。 /// ・クライアントは"access_token"を取得する。 /// </summary> /// <param name="context">OAuthGrantClientCredentialsContext</param> /// <returns>Task</returns> /// <see cref="https://msdn.microsoft.com/ja-jp/library/dn343586.aspx"/> public override async Task GrantClientCredentials(OAuthGrantClientCredentialsContext context) { if (!ASPNETIdentityConfig.EnableClientCredentialsGrantType) { throw new NotSupportedException(Resources.ApplicationOAuthBearerTokenProvider.EnableClientCredentialsGrantType); } // ASP.Net MVC: Creating an OAuth client credentials grant type token endpoint // http://www.hackered.co.uk/articles/asp-net-mvc-creating-an-oauth-client-credentials-grant-type-token-endpoint // WEB API 2 OAuth Client Credentials Authentication, How to add additional parameters? - Stack Overflow // http://stackoverflow.com/questions/29132031/web-api-2-oauth-client-credentials-authentication-how-to-add-additional-paramet try { ApplicationUser user = null; ApplicationUserManager userManager = HttpContext.Current.GetOwinContext().GetUserManager <ApplicationUserManager>(); // client_idに対応するApplicationUserを取得する。 user = await userManager.FindByNameAsync( OAuth2Helper.GetInstance().GetClientName(context.ClientId)); // ClaimsIdentity ClaimsIdentity identity = null; if (user != null) { // User Accountの場合、 // ユーザーに対応するClaimsIdentityを生成する。 identity = await userManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType); // ClaimsIdentityに、その他、所定のClaimを追加する。 identity = OAuth2Helper.AddClaim(identity, context.ClientId, "", context.Scope, ""); // オペレーション・トレース・ログ出力 Logging.MyOperationTrace( string.Format("{0}({1}) passed the 'client credentials flow' by {2}({3}).", user.Id, user.UserName, context.ClientId, OAuth2Helper.GetInstance().GetClientName(context.ClientId))); // 検証完了 context.Validated(identity); } else { // Client Accountの場合、 string clientName = OAuth2Helper.GetInstance().GetClientName(context.ClientId); if (string.IsNullOrEmpty(clientName)) { // 検証失敗 context.Rejected(); } else { // ClaimsIdentityを自前で生成する。 identity = new ClaimsIdentity(context.Options.AuthenticationType); // Name Claimを追加 identity.AddClaim(new Claim(ClaimTypes.Name, OAuth2Helper.GetInstance().GetClientName(context.ClientId))); // ClaimsIdentityに、その他、所定のClaimを追加する。 identity = OAuth2Helper.AddClaim(identity, context.ClientId, "", context.Scope, ""); // オペレーション・トレース・ログ出力 Logging.MyOperationTrace(string.Format( "Passed the 'client credentials flow' by {0}({1}).", context.ClientId, clientName)); // 検証完了 context.Validated(identity); } } } catch { // ユーザーを取得できませんでした。 context.SetError( "server_error", Resources.ApplicationOAuthBearerTokenProvider.server_error1); // 拒否 context.Rejected(); } }
/// <summary> /// Resource Owner Password Credentials Grantのカスタム認証ロジック /// TokenEndpointPathへの grant_type = password アクセスは、こちらに到達する。 /// ・context.Username および context.Password を検証する。 /// ・クライアントは"access_token" および "refresh_token" を取得する。 /// </summary> /// <param name="context">OAuthGrantResourceOwnerCredentialsContext</param> /// <returns>Task</returns> /// <see cref="https://msdn.microsoft.com/ja-jp/library/dn343587.aspx"/> public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { if (!ASPNETIdentityConfig.EnableResourceOwnerPasswordCredentialsGrantType) { throw new NotSupportedException(Resources.ApplicationOAuthBearerTokenProvider.EnableResourceOwnerCredentialsGrantType); } // この実装は、ValidateClientAuthenticationの続きで、ClientのOAuth権限を確認する。 // 権限がある場合、Resource Owner Password Credentialsグラント種別の処理フローを継続する。 try { // ApplicationUser を取得する。 ApplicationUserManager userManager = HttpContext.Current.GetOwinContext().GetUserManager <ApplicationUserManager>(); // username=ユーザ名&password=パスワードとして送付されたクレデンシャルを検証する。 ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password); if (user != null) { // ユーザーが見つかった場合。 try { // ユーザーに対応するClaimsIdentityを生成する。 ClaimsIdentity identity = await userManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType); // ClaimsIdentityに、その他、所定のClaimを追加する。 OAuth2Helper.AddClaim(identity, context.ClientId, "", context.Scope, ""); // 検証完了 context.Validated(identity); // オペレーション・トレース・ログ出力 Logging.MyOperationTrace(string.Format("{0}({1}) passed the 'resource owner password credentials flow' by {2}({3}).", user.Id, user.UserName, context.ClientId, OAuth2Helper.GetInstance().GetClientName(context.ClientId))); } catch { // ClaimManagerIdentityは、UserManagerで作成できませんでした。 context.SetError( "server_error", Resources.ApplicationOAuthBearerTokenProvider.server_error2); // 拒否 context.Rejected(); } } else { // ユーザーが見つからなかった場合。 // Resources Ownerの資格情報が無効であるか、Resources Ownerが存在しません。 context.SetError( "access_denied", Resources.ApplicationOAuthBearerTokenProvider.access_denied); // 拒否 context.Rejected(); } } catch { // ユーザーを取得できませんでした。 context.SetError( "server_error", Resources.ApplicationOAuthBearerTokenProvider.server_error1); // 拒否 context.Rejected(); } }
/// <summary> /// Authorization Code、Resource Owner Password Credentialsl、Client Credentialsグラント種別において、 /// OAuthBearerTokenEndpointPathを処理する場合に発生する、" クライアント認証 " を行なうメソッド。 /// " クライアント認証 "では、以下の両方の要素を検証する。 /// ・context.ClientId が、登録された "client_id" であること。 /// ・その他、資格情報が要求に存在していることを検証する。 /// </summary> /// <param name="context">OAuthValidateClientAuthenticationContext</param> /// <returns>Task</returns> /// <see cref="https://msdn.microsoft.com/ja-jp/library/dn385497.aspx"/> public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { // クライアント識別子 string clientId = ""; string clientSecret = ""; // ・context.Validated を呼び出し、contextを検証完了に設定する。 // ・検証完了にしなければ要求はそれ以上先には進まない。 //context.Validated(clientId); #region クライアント認証を行なう。 if (string.IsNullOrEmpty(context.Parameters[OAuth2AndOIDCConst.grant_type])) { // 指定なし。 // 検証未完 } else if (context.Parameters[OAuth2AndOIDCConst.grant_type].ToLower() == OAuth2AndOIDCConst.AuthorizationCodeGrantType) { #region Authorization Codeグラント種別 // "client_id" および "client_secret"を基本認証の認証ヘッダから取得 if (context.TryGetBasicCredentials(out clientId, out clientSecret)) { // 通常のクライアント認証 if (!(string.IsNullOrEmpty(clientId) && string.IsNullOrEmpty(clientSecret))) { // *.config or OAuth2Dataテーブルを参照してクライアント認証を行なう。 if (clientSecret == OAuth2Helper.GetInstance().GetClientSecret(context.ClientId)) { // 検証完了 context.Validated(clientId); } } } else { // その他のクライアント認証の可能性 string assertion = context.Parameters.Get(OAuth2AndOIDCConst.assertion); if (!string.IsNullOrEmpty(assertion)) { // JWT client assertion Dictionary <string, string> dic = JsonConvert.DeserializeObject <Dictionary <string, string> >( CustomEncode.ByteToString(CustomEncode.FromBase64UrlString( assertion.Split('.')[1]), CustomEncode.us_ascii)); string pubKey = OAuth2Helper.GetInstance().GetJwtAssertionPublickey(dic[OAuth2AndOIDCConst.iss]); pubKey = CustomEncode.ByteToString(CustomEncode.FromBase64String(pubKey), CustomEncode.us_ascii); if (!string.IsNullOrEmpty(pubKey)) { string iss = ""; string aud = ""; string scopes = ""; JObject jobj = null; if (JwtAssertion.VerifyJwtBearerTokenFlowAssertion( assertion, out iss, out aud, out scopes, out jobj, pubKey)) { // aud 検証 if (aud == ASPNETIdentityConfig.OAuth2AuthorizationServerEndpointsRootURI + ASPNETIdentityConfig.OAuth2BearerTokenEndpoint) { // 検証完了 context.Validated(iss); } } } } else { // クライアント認証なしエラー } } #endregion } else if (context.Parameters[OAuth2AndOIDCConst.grant_type].ToLower() == OAuth2AndOIDCConst.ResourceOwnerPasswordCredentialsGrantType) { #region Resource Owner Password Credentialsグラント種別 #region 参考 // Simple OAuth Server: Implementing a Simple OAuth Server with Katana // OAuth Authorization Server Components (Part 1) - Tugberk Ugurlu's Blog // http://www.tugberkugurlu.com/archive/simple-oauth-server-implementing-a-simple-oauth-server-with-katana-oauth-authorization-server-components-part-1 // ・・・ 基本認証を使用する既存のクライアントを認証してOAuthに移行する。 #endregion // "client_id" および "client_secret"を基本認証の認証ヘッダから取得 if (context.TryGetBasicCredentials(out clientId, out clientSecret)) { if (!(string.IsNullOrEmpty(clientId) && string.IsNullOrEmpty(clientSecret))) { // *.config or OAuth2Dataテーブルを参照してクライアント認証を行なう。 if (clientSecret == OAuth2Helper.GetInstance().GetClientSecret(context.ClientId)) { // 検証完了 context.Validated(clientId); } } } #endregion } else if (context.Parameters[OAuth2AndOIDCConst.grant_type].ToLower() == OAuth2AndOIDCConst.ClientCredentialsGrantType) { #region Client Credentialsグラント種別 // "client_id" および "client_secret"を基本認証の認証ヘッダから取得 if (context.TryGetBasicCredentials(out clientId, out clientSecret)) { if (!(string.IsNullOrEmpty(clientId) && string.IsNullOrEmpty(clientSecret))) { // *.config or OAuth2Dataテーブルを参照してクライアント認証を行なう。 if (clientSecret == OAuth2Helper.GetInstance().GetClientSecret(context.ClientId)) { // 検証完了 context.Validated(clientId); } } } #endregion } else if (context.Parameters[OAuth2AndOIDCConst.grant_type].ToLower() == OAuth2AndOIDCConst.RefreshTokenGrantType) { #region RefreshToken if (!ASPNETIdentityConfig.EnableRefreshToken) { throw new NotSupportedException(Resources.ApplicationOAuthBearerTokenProvider.EnableRefreshToken); } // "client_id" および "client_secret"を基本認証の認証ヘッダから取得 if (context.TryGetBasicCredentials(out clientId, out clientSecret)) { if (!(string.IsNullOrEmpty(clientId) && string.IsNullOrEmpty(clientSecret))) { // *.config or OAuth2Dataテーブルを参照してクライアント認証を行なう。 if (clientSecret == OAuth2Helper.GetInstance().GetClientSecret(context.ClientId)) { // 検証完了 context.Validated(clientId); } } } #endregion } else { // 不明な値 // 検証未完 } #endregion // 結果を返す。 return(Task.FromResult(0)); }
/// <summary> /// Authorization Code、Resource Owner Password Credentialsl、Client Credentialsグラント種別において、 /// OAuthBearerTokenEndpointPathを処理する場合に発生する、" クライアント認証 " を行なうメソッド。 /// " クライアント認証 "では、以下の両方の要素を検証する。 /// ・context.ClientId が、登録された "client_id" であること。 /// ・その他、資格情報が要求に存在していることを検証する。 /// </summary> /// <param name="context">OAuthValidateClientAuthenticationContext</param> /// <returns>Task</returns> /// <see cref="https://msdn.microsoft.com/ja-jp/library/dn385497.aspx"/> public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { // クライアント識別子 string clientId = ""; string clientSecret = ""; // ・context.Validated を呼び出し、contextを検証完了に設定する。 // ・検証完了にしなければ要求はそれ以上先には進まない。 //context.Validated(clientId); #region クライアント認証を行なう。 if (string.IsNullOrEmpty(context.Parameters["grant_type"])) { // 指定なし。 // 検証未完 } else if (context.Parameters["grant_type"].ToLower() == ASPNETIdentityConst.AuthorizationCodeGrantType) { #region Authorization Codeグラント種別 // "client_id" および "client_secret"を基本認証の認証ヘッダから取得 if (context.TryGetBasicCredentials(out clientId, out clientSecret)) { if (!(string.IsNullOrEmpty(clientId) && string.IsNullOrEmpty(clientSecret))) { // *.config or OAuth2Dataテーブルを参照してクライアント認証を行なう。 if (clientSecret == OAuth2Helper.GetInstance().GetClientSecret(context.ClientId)) { // 検証完了 context.Validated(clientId); } } } #endregion } else if (context.Parameters["grant_type"].ToLower() == ASPNETIdentityConst.ResourceOwnerPasswordCredentialsGrantType) { #region Resource Owner Password Credentialsグラント種別 #region 参考 // Simple OAuth Server: Implementing a Simple OAuth Server with Katana // OAuth Authorization Server Components (Part 1) - Tugberk Ugurlu's Blog // http://www.tugberkugurlu.com/archive/simple-oauth-server-implementing-a-simple-oauth-server-with-katana-oauth-authorization-server-components-part-1 // ・・・ 基本認証を使用する既存のクライアントを認証してOAuthに移行する。 #endregion // "client_id" および "client_secret"を基本認証の認証ヘッダから取得 if (context.TryGetBasicCredentials(out clientId, out clientSecret)) { if (!(string.IsNullOrEmpty(clientId) && string.IsNullOrEmpty(clientSecret))) { // *.config or OAuth2Dataテーブルを参照してクライアント認証を行なう。 if (clientSecret == OAuth2Helper.GetInstance().GetClientSecret(context.ClientId)) { // 検証完了 context.Validated(clientId); } } } #endregion } else if (context.Parameters["grant_type"].ToLower() == ASPNETIdentityConst.ClientCredentialsGrantType) { #region Client Credentialsグラント種別 // "client_id" および "client_secret"を基本認証の認証ヘッダから取得 if (context.TryGetBasicCredentials(out clientId, out clientSecret)) { if (!(string.IsNullOrEmpty(clientId) && string.IsNullOrEmpty(clientSecret))) { // *.config or OAuth2Dataテーブルを参照してクライアント認証を行なう。 if (clientSecret == OAuth2Helper.GetInstance().GetClientSecret(context.ClientId)) { // 検証完了 context.Validated(clientId); } } } #endregion } else if (context.Parameters["grant_type"].ToLower() == ASPNETIdentityConst.RefreshTokenGrantType) { #region RefreshToken if (!ASPNETIdentityConfig.EnableRefreshToken) { throw new NotSupportedException(Resources.ApplicationOAuthBearerTokenProvider.EnableRefreshToken); } // "client_id" および "client_secret"を基本認証の認証ヘッダから取得 if (context.TryGetBasicCredentials(out clientId, out clientSecret)) { if (!(string.IsNullOrEmpty(clientId) && string.IsNullOrEmpty(clientSecret))) { // *.config or OAuth2Dataテーブルを参照してクライアント認証を行なう。 if (clientSecret == OAuth2Helper.GetInstance().GetClientSecret(context.ClientId)) { // 検証完了 context.Validated(clientId); } } } #endregion } else { // 不明な値 // 検証未完 } #endregion // 結果を返す。 return(Task.FromResult(0)); }
/// <summary>Protect</summary> /// <param name="ticket">AuthenticationTicket</param> /// <returns>JWT文字列</returns> public string Protect(AuthenticationTicket ticket) { string json = ""; string jwt = ""; // チェック if (ticket == null) { throw new ArgumentNullException("ticket"); } ApplicationUserManager userManager = HttpContext.Current.GetOwinContext().GetUserManager <ApplicationUserManager>(); ApplicationUser user = null; if (ticket.Identity.Name == null) { // Client認証の場合、 user = null; } else { // Resource Owner認証の場合、 user = userManager.FindByName(ticket.Identity.Name); // 同期版でOK。 } #region ClaimSetの生成 Dictionary <string, object> authTokenClaimSet = new Dictionary <string, object>(); List <string> scopes = new List <string>(); List <string> roles = new List <string>(); foreach (Claim c in ticket.Identity.Claims) { if (c.Type == ASPNETIdentityConst.Claim_Issuer) { authTokenClaimSet.Add("iss", c.Value); } else if (c.Type == ASPNETIdentityConst.Claim_Audience) { authTokenClaimSet.Add("aud", c.Value); } else if (c.Type == ASPNETIdentityConst.Claim_Nonce) { authTokenClaimSet.Add("nonce", c.Value); } else if (c.Type == ASPNETIdentityConst.Claim_Scope) { scopes.Add(c.Value); } else if (c.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role") { roles.Add(c.Value); } } if (ticket.Identity.Name == null) { // Client認証の場合、aud(client_id)に対応するClient名称 authTokenClaimSet.Add("sub", OAuth2Helper.GetInstance().GetClientName((string)authTokenClaimSet["aud"])); } else { // Resource Owner認証の場合、Resource Ownerの名称 authTokenClaimSet.Add("sub", ticket.Identity.Name); } authTokenClaimSet.Add("exp", ticket.Properties.ExpiresUtc.Value.ToUnixTimeSeconds().ToString()); authTokenClaimSet.Add("nbf", DateTimeOffset.Now.ToUnixTimeSeconds().ToString()); authTokenClaimSet.Add("iat", ticket.Properties.IssuedUtc.Value.ToUnixTimeSeconds().ToString()); authTokenClaimSet.Add("jti", Guid.NewGuid().ToString("N")); authTokenClaimSet.Add("scopes", scopes); // scope値によって、返す値を変更する。 foreach (string scope in scopes) { if (user != null) { // user == null では NG な Resource(Resource Owner の Resource) switch (scope.ToLower()) { #region OpenID Connect case ASPNETIdentityConst.Scope_Profile: // ・・・ break; case ASPNETIdentityConst.Scope_Email: authTokenClaimSet.Add("email", user.Email); authTokenClaimSet.Add("email_verified", user.EmailConfirmed.ToString()); break; case ASPNETIdentityConst.Scope_Phone: authTokenClaimSet.Add("phone_number", user.PhoneNumber); authTokenClaimSet.Add("phone_number_verified", user.PhoneNumberConfirmed.ToString()); break; case ASPNETIdentityConst.Scope_Address: // ・・・ break; #endregion #region Else case ASPNETIdentityConst.Scope_Userid: authTokenClaimSet.Add(ASPNETIdentityConst.Scope_Userid, user.Id); break; case ASPNETIdentityConst.Scope_Roles: authTokenClaimSet.Add( ASPNETIdentityConst.Scope_Roles, userManager.GetRolesAsync(user.Id).Result); break; #endregion } } else { // user == null でも OK な Resource } } json = JsonConvert.SerializeObject(authTokenClaimSet); #endregion #region JWT化 JWT_RS256 jwtRS256 = null; // 署名 jwtRS256 = new JWT_RS256(ASPNETIdentityConfig.OAuthJWT_pfx, ASPNETIdentityConfig.OAuthJWTPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); jwt = jwtRS256.Create(json); // 検証 jwtRS256 = new JWT_RS256(ASPNETIdentityConfig.OAuthJWT_cer, ASPNETIdentityConfig.OAuthJWTPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); if (jwtRS256.Verify(jwt)) { return(jwt); // 検証できた。 } else { return(""); // 検証できなかった。 } #endregion }
/// <summary>Unprotect</summary> /// <param name="jwt">JWT文字列</param> /// <returns>AuthenticationTicket</returns> public AuthenticationTicket Unprotect(string jwt) { // 空のケースあり。 if (string.IsNullOrEmpty(jwt)) { return(null); } // 検証 JWT_RS256 jwtRS256 = new JWT_RS256(ASPNETIdentityConfig.OAuthJWT_cer, ASPNETIdentityConfig.OAuthJWTPassword); if (jwtRS256.Verify(jwt)) { // 検証できた。 // デシリアライズ、 string[] temp = jwt.Split('.'); string json = CustomEncode.ByteToString(CustomEncode.FromBase64UrlString(temp[1]), CustomEncode.UTF_8); Dictionary <string, object> authTokenClaimSet = JsonConvert.DeserializeObject <Dictionary <string, object> >(json); // 以下の検証処理 // ★ "iss": accounts.google.com的な, // ★ "aud": client_id(クライアント識別子) // ★ "sub": ユーザーの一意識別子(uname, email) // ★ "exp": JWT の有効期限(Unix時間) // ☆ "jti": JWT のID(OAuth Token Revocation) DateTime?datetime = OAuth2RevocationProvider.GetInstance().Get((string)authTokenClaimSet["jti"]); if (datetime == null) { // authToken.iss, authToken.expの検証 if ((string)authTokenClaimSet["iss"] == ASPNETIdentityConfig.OAuthIssuerId && OAuth2Helper.GetInstance().GetClientSecret((string)authTokenClaimSet["aud"]) != null && long.Parse((string)authTokenClaimSet["exp"]) >= DateTimeOffset.Now.ToUnixTimeSeconds()) { // authToken.subの検証 // ApplicationUser を取得する。 ApplicationUserManager userManager = HttpContext.Current.GetOwinContext().GetUserManager <ApplicationUserManager>(); ApplicationUser user = userManager.FindByName((string)authTokenClaimSet["sub"]); // 同期版でOK。 if (user != null) { // User Accountの場合 // ユーザーに対応するClaimsIdentityを生成し、 ClaimsIdentity identity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ExternalBearer); // aud、scopes、nonceなどのClaimを追加する。 List <string> scopes = new List <string>(); foreach (string s in (JArray)authTokenClaimSet["scopes"]) { scopes.Add(s); } OAuth2Helper.AddClaim(identity, (string)authTokenClaimSet["aud"], "", scopes, (string) authTokenClaimSet["nonce"]); // その他、所定のClaimを追加する。 identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_ExpirationTime, (string)authTokenClaimSet["exp"])); identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_NotBefore, (string)authTokenClaimSet["nbf"])); identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_IssuedAt, (string)authTokenClaimSet["iat"])); identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_JwtId, (string)authTokenClaimSet["jti"])); // AuthenticationPropertiesの生成 AuthenticationProperties prop = new AuthenticationProperties(); prop.IssuedUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet["iat"])); prop.ExpiresUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet["exp"])); AuthenticationTicket auth = new AuthenticationTicket(identity, prop); // 認証結果を返す。 return(auth); } else { // Client Accountの場合 // ClaimとStoreのAudience(aud)に対応するSubject(sub)が一致するかを確認し、一致する場合のみ、認証する。 // ※ でないと、UserStoreから削除されたUser Accountが、Client Accountに化けることになる。 if ((string)authTokenClaimSet["sub"] == OAuth2Helper.GetInstance().GetClientName((string)authTokenClaimSet["aud"])) { // ClaimsIdentityを生成し、 ClaimsIdentity identity = new ClaimsIdentity(DefaultAuthenticationTypes.ExternalBearer); // sub(client_idに対応するclient_name)Claimを設定する。 identity.AddClaim(new Claim(ClaimTypes.Name, (string)authTokenClaimSet["sub"])); // aud、scopes、nonceなどのClaimを追加する。 List <string> scopes = new List <string>(); foreach (string s in (JArray)authTokenClaimSet["scopes"]) { scopes.Add(s); } OAuth2Helper.AddClaim(identity, (string)authTokenClaimSet["aud"], "", scopes, (string)authTokenClaimSet["nonce"]); // その他、所定のClaimを追加する。 identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_ExpirationTime, (string)authTokenClaimSet["exp"])); identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_NotBefore, (string)authTokenClaimSet["nbf"])); identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_IssuedAt, (string)authTokenClaimSet["iat"])); identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_JwtId, (string)authTokenClaimSet["jti"])); // AuthenticationPropertiesの生成 AuthenticationProperties prop = new AuthenticationProperties(); prop.IssuedUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet["iat"])); prop.ExpiresUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet["exp"])); AuthenticationTicket auth = new AuthenticationTicket(identity, prop); // 認証結果を返す。 return(auth); } } } else { // クレーム検証の失敗 } } else { // 取り消し済み } } else { // JWT署名検証の失敗 } // 検証、認証ナドナド、できなかった。 return(null); }
/// <summary>Protect</summary> /// <param name="ticket">AuthenticationTicket</param> /// <returns>JWT文字列</returns> public string Protect(AuthenticationTicket ticket) { string json = ""; //string jws = ""; // チェック if (ticket == null) { throw new ArgumentNullException("ticket"); } ApplicationUserManager userManager = HttpContext.Current.GetOwinContext().GetUserManager <ApplicationUserManager>(); ApplicationUser user = null; if (ticket.Identity.Name == null) { // Client認証の場合、 user = null; } else { // Resource Owner認証の場合、 user = userManager.FindByName(ticket.Identity.Name); // 同期版でOK。 } #region ClaimSetの生成 Dictionary <string, object> authTokenClaimSet = new Dictionary <string, object>(); List <string> scopes = new List <string>(); List <string> roles = new List <string>(); foreach (Claim c in ticket.Identity.Claims) { if (c.Type == OAuth2AndOIDCConst.Claim_Issuer) { authTokenClaimSet.Add(OAuth2AndOIDCConst.iss, c.Value); } else if (c.Type == OAuth2AndOIDCConst.Claim_Audience) { authTokenClaimSet.Add(OAuth2AndOIDCConst.aud, c.Value); } else if (c.Type == OAuth2AndOIDCConst.Claim_Nonce) { authTokenClaimSet.Add(OAuth2AndOIDCConst.nonce, c.Value); } else if (c.Type == OAuth2AndOIDCConst.Claim_Scopes) { scopes.Add(c.Value); } else if (c.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role") { roles.Add(c.Value); } } if (ticket.Identity.Name == null) { // Client認証の場合、aud(client_id)に対応するClient名称 authTokenClaimSet.Add(OAuth2AndOIDCConst.sub, OAuth2Helper.GetInstance().GetClientName((string)authTokenClaimSet[OAuth2AndOIDCConst.aud])); } else { // Resource Owner認証の場合、Resource Ownerの名称 authTokenClaimSet.Add(OAuth2AndOIDCConst.sub, ticket.Identity.Name); } authTokenClaimSet.Add(OAuth2AndOIDCConst.exp, ticket.Properties.ExpiresUtc.Value.ToUnixTimeSeconds().ToString()); authTokenClaimSet.Add(OAuth2AndOIDCConst.nbf, DateTimeOffset.Now.ToUnixTimeSeconds().ToString()); authTokenClaimSet.Add(OAuth2AndOIDCConst.iat, ticket.Properties.IssuedUtc.Value.ToUnixTimeSeconds().ToString()); authTokenClaimSet.Add(OAuth2AndOIDCConst.jti, Guid.NewGuid().ToString("N")); authTokenClaimSet.Add(OAuth2AndOIDCConst.scopes, scopes); // scope値によって、返す値を変更する。 foreach (string scope in scopes) { if (user != null) { // user == null では NG な Resource(Resource Owner の Resource) switch (scope.ToLower()) { #region OpenID Connect case OAuth2AndOIDCConst.Scope_Profile: // ・・・ break; case OAuth2AndOIDCConst.Scope_Email: authTokenClaimSet.Add(OAuth2AndOIDCConst.Scope_Email, user.Email); authTokenClaimSet.Add(OAuth2AndOIDCConst.email_verified, user.EmailConfirmed.ToString()); break; case OAuth2AndOIDCConst.Scope_Phone: authTokenClaimSet.Add(OAuth2AndOIDCConst.phone_number, user.PhoneNumber); authTokenClaimSet.Add(OAuth2AndOIDCConst.phone_number_verified, user.PhoneNumberConfirmed.ToString()); break; case OAuth2AndOIDCConst.Scope_Address: // ・・・ break; #endregion #region Else case OAuth2AndOIDCConst.Scope_UserID: authTokenClaimSet.Add(OAuth2AndOIDCConst.Scope_UserID, user.Id); break; case OAuth2AndOIDCConst.Scope_Roles: authTokenClaimSet.Add( OAuth2AndOIDCConst.Scope_Roles, userManager.GetRolesAsync(user.Id).Result); break; #endregion } } else { // user == null でも OK な Resource } } json = JsonConvert.SerializeObject(authTokenClaimSet); #endregion #region JWS化 JWS_RS256_X509 jwsRS256 = null; // JWT_RS256_X509 jwsRS256 = new JWS_RS256_X509(ASPNETIdentityConfig.OAuth2JWT_pfx, ASPNETIdentityConfig.OAuth2JWTPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); // JWSHeaderのセット // kid : https://openid-foundation-japan.github.io/rfc7638.ja.html#Example Dictionary <string, string> jwk = JsonConvert.DeserializeObject <Dictionary <string, string> >( RS256_KeyConverter.X509PfxToJwkPublicKey(ASPNETIdentityConfig.OAuth2JWT_pfx, ASPNETIdentityConfig.OAuth2JWTPassword)); jwsRS256.JWSHeader.kid = jwk[JwtConst.kid]; jwsRS256.JWSHeader.jku = ASPNETIdentityConfig.OAuth2AuthorizationServerEndpointsRootURI + OAuth2AndOIDCParams.JwkSetUri; // 署名 return(jwsRS256.Create(json)); //// 検証 //jwsRS256 = new JWS_RS256_X509(OAuth2AndOIDCParams.RS256Cer, ASPNETIdentityConfig.OAuth2JWTPassword, // X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); //if (jwsRS256.Verify(jws)) //{ // return jws; // 検証できた。 //} //else //{ // return ""; // 検証できなかった。 //} #endregion }