/// <summary> /// Authenticates the client. Used when authenticating with the authorization_code grant type. /// </summary> /// <param name="clientId">The client id.</param> /// <param name="redirectUri">The redirect URI.</param> /// <returns>The client principal.</returns> public override async Task<ISentinelPrincipal> AuthenticateClientAsync(string clientId, string redirectUri) { using (var connection = this.OpenConnection()) { var transaction = connection.BeginTransaction(); var matches = await connection.QueryAsync<Client>( "SELECT * FROM Clients WHERE ClientId = @UserName AND RedirectUri = @RedirectUri AND Enabled = 1", new { UserName = clientId, RedirectUri = redirectUri }, transaction); if (matches.Any()) { var principal = new SentinelPrincipal( new SentinelIdentity( AuthenticationType.OAuth, new SentinelClaim(ClaimTypes.Name, clientId))); return principal; } return SentinelPrincipal.Anonymous; } }
private async Task OnPrincipalCreated(PrincipalCreatedEventArgs args) { var principal = new SentinelPrincipal(args.Principal); // Add name identifier claim to support MVC AntiForgeryToken principal.Identity.AddClaim(ClaimTypes.NameIdentifier, principal.Identity.Name); // Replace principal to make Sentinel use the new principal for the access token args.Principal = principal; }
/// <summary>Authenticates the user using username and password.</summary> /// <param name="username">The username.</param> /// <param name="password">The password.</param> /// <returns>The client principal.</returns> /// <exception cref="SqlException">Parallel transactions are not allowed when using Multiple Active Result Sets (MARS).</exception> public async override Task<ISentinelPrincipal> AuthenticateUserWithPasswordAsync(string username, string password) { using (var connection = this.OpenConnection()) { var transaction = connection.BeginTransaction(); var matches = await connection.QueryAsync<User>( "SELECT * FROM Users WHERE UserName = @UserName", new { UserName = username }, transaction); var user = matches.FirstOrDefault(); if (user != null && this.CryptoProvider.ValidateHash(password, user.Password)) { var principal = new SentinelPrincipal( new SentinelIdentity( AuthenticationType.OAuth, new SentinelClaim(ClaimTypes.Name, user.Username), new SentinelClaim(ClaimTypes.GivenName, user.FirstName), new SentinelClaim(ClaimTypes.Surname, user.LastName))); // Update last login date await connection.ExecuteAsync( "UPDATE Users SET LastLogin = @LastLogin WHERE Username = @Username", new { LastLogin = DateTime.UtcNow, Username = user.Username }, transaction); return principal; } return SentinelPrincipal.Anonymous; } }
/// <summary> /// Converts the claim principal to a json string /// </summary> /// <param name="principal">The principal.</param> /// <returns>A json string representing the claims principal.</returns> public static string ToJson(this IPrincipal principal) { var p = new SentinelPrincipal(principal); return JsonConvert.SerializeObject(p); }
/// <summary> /// Called when a request to the Token endpoint arrives with a "grant_type" of "refresh_token". This occurs if your application has issued a "refresh_token" /// along with the "access_token", and the client is attempting to use the "refresh_token" to acquire a new "access_token", and possibly a new "refresh_token". /// To issue a refresh token the an Options.RefreshTokenProvider must be assigned to create the value which is returned. The claims and properties /// associated with the refresh token are present in the context.Ticket. The application must call context.Validated to instruct the /// Authorization Server middleware to issue an access token based on those claims and properties. The call to context.Validated may /// be given a different AuthenticationTicket or ClaimsIdentity in order to control which information flows from the refresh token to /// the access token. The default behavior when using the OAuthAuthorizationServerProvider is to flow information from the refresh token to /// the access token unmodified. /// See also http://tools.ietf.org/html/rfc6749#section-6 /// </summary> /// <param name="context">The context of the event carries information in and results out.</param> /// <returns> /// Task to enable asynchronous execution /// </returns> public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context) { this.options.Logger.Debug("Authenticating refresh token flow"); var user = new SentinelPrincipal(context.Ticket.Identity); // Add grant type claim user.Identity.RemoveClaim(x => x.Type == ClaimType.GrantType); user.Identity.AddClaim(ClaimType.GrantType, GrantType.RefreshToken); // Activate event if subscribed to if (this.options.Events.PrincipalCreated != null) { var args = new PrincipalCreatedEventArgs(user, context); await this.options.Events.PrincipalCreated(args); user = new SentinelPrincipal(args.Principal); } context.Validated(user.Identity.AsClaimsIdentity()); }
/// <summary> /// Called when a request to the Token endpoint arrives with a "grant_type" of "authorization_code". This occurs after the Authorize /// endpoint as redirected the user-agent back to the client with a "code" parameter, and the client is exchanging that for an "access_token". /// The claims and properties /// associated with the authorization code are present in the context.Ticket. The application must call context.Validated to instruct the Authorization /// Server middleware to issue an access token based on those claims and properties. The call to context.Validated may be given a different /// AuthenticationTicket or ClaimsIdentity in order to control which information flows from authorization code to access token. /// The default behavior when using the OAuthAuthorizationServerProvider is to flow information from the authorization code to /// the access token unmodified. /// See also http://tools.ietf.org/html/rfc6749#section-4.1.3 /// </summary> /// <param name="context">The context of the event carries information in and results out.</param> /// <returns>Task to enable asynchronous execution</returns> public override async Task GrantAuthorizationCode(OAuthGrantAuthorizationCodeContext context) { this.options.Logger.Debug("Authenticating authorization code flow"); var user = new SentinelPrincipal(context.Ticket.Identity); // Add grant type claim user.Identity.RemoveClaim(x => x.Type == ClaimType.GrantType); user.Identity.AddClaim(ClaimType.GrantType, GrantType.AuthorizationCode); context.Validated(user.Identity.AsClaimsIdentity()); }
/// <summary> /// Called when a request to the Token endpoint arrives with a "grant_type" of "password". This occurs when the user has provided name and password /// credentials directly into the client application's user interface, and the client application is using those to acquire an "access_token" and /// optional "refresh_token". If the web application supports the /// resource owner credentials grant type it must validate the context.Username and context.Password as appropriate. To issue an /// access token the context.Validated must be called with a new ticket containing the claims about the resource owner which should be associated /// with the access token. The application should take appropriate measures to ensure that the endpoint isn’t abused by malicious callers. /// The default behavior is to reject this grant type. /// See also http://tools.ietf.org/html/rfc6749#section-4.3.2 /// </summary> /// <param name="context">The context of the event carries information in and results out.</param> /// <returns>Task to enable asynchronous execution</returns> public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { this.options.Logger.DebugFormat("Authenticating resource owner flow for user '{0}'", Regex.Escape(context.UserName)); var user = await this.options.UserManager.AuthenticateUserWithPasswordAsync(context.UserName, context.Password); if (!user.Identity.IsAuthenticated) { context.Rejected(); this.options.Logger.WarnFormat("User '{0}' was not authenticated", Regex.Escape(context.UserName)); return; } // Add oauth claims user.Identity.AddClaim(ClaimType.Client, context.ClientId); user.Identity.RemoveClaim(x => x.Type == ClaimType.GrantType); user.Identity.AddClaim(ClaimType.GrantType, GrantType.Password); // Activate event if subscribed to if (this.options.Events.PrincipalCreated != null) { var args = new PrincipalCreatedEventArgs(user, context); await this.options.Events.PrincipalCreated(args); user = new SentinelPrincipal(args.Principal); } // Convert to proper authentication type var principal = this.options.PrincipalProvider.Create(context.Options.AuthenticationType, user.Identity.Claims.ToArray()); // Validate ticket var ticket = new AuthenticationTicket(principal.Identity.AsClaimsIdentity(), new AuthenticationProperties()); context.Validated(ticket); this.options.Logger.DebugFormat("User '{0}' was successfully authenticated", Regex.Escape(context.UserName)); }
/// <summary>Authenticates the client credentials using client id and secret.</summary> /// <param name="clientId">The client id.</param> /// <param name="clientSecret">The client secret.</param> /// <returns>The client principal.</returns> public override async Task<ISentinelPrincipal> AuthenticateClientCredentialsAsync(string clientId, string clientSecret) { using (var connection = this.OpenConnection()) { var transaction = connection.BeginTransaction(); var matches = await connection.QueryAsync<Client>( "SELECT * FROM Clients WHERE ClientId = @UserName AND Enabled = 1", new { UserName = clientId }, transaction); foreach (var client in matches) { if (this.CryptoProvider.ValidateHash(clientSecret, client.ClientSecret)) { var principal = new SentinelPrincipal( new SentinelIdentity( AuthenticationType.OAuth, new SentinelClaim(ClaimTypes.Name, client.ClientId))); return principal; } } return SentinelPrincipal.Anonymous; } }
public void Decrypt_WhenGivenEncryptedString_ReturnsDecryptedString(string key) { var c1 = new SentinelPrincipal(new SentinelIdentity(AuthenticationType.OAuth, new SentinelClaim(ClaimTypes.Name, "azzlack"))); var s = JsonConvert.SerializeObject(c1); var e = this.provider.Encrypt(s, key); Console.WriteLine("Original: {0}", s); Console.WriteLine(); Console.WriteLine("Encrypted: {0}", e); Console.WriteLine(); var d = this.provider.Decrypt(e, key); Console.WriteLine("Decrypted: {0}", d); var c2 = JsonConvert.DeserializeObject<SentinelPrincipal>(d); Assert.AreEqual(c1.Identity.Name, c2.Identity.Name); }