/// <summary>
        /// Represents an event called for each request to the authorization endpoint
        /// to determine if the request is valid and should continue.
        /// </summary>
        /// <param name="context">The context instance associated with this event.</param>
        public override async Task ValidateAuthorizationRequest(ValidateAuthorizationRequestContext context)
        {
            // Note: the OpenID Connect server middleware supports the authorization code, implicit and hybrid flows
            // but this authorization provider only accepts response_type=code authorization/authentication requests.
            // You may consider relaxing it to support the implicit or hybrid flows. In this case, consider adding
            // checks rejecting implicit/hybrid authorization requests when the client is a confidential application.
            if (!context.Request.IsAuthorizationCodeFlow())
            {
                context.Reject(
                    error: OpenIdConnectConstants.Errors.UnsupportedResponseType,
                    description: "Only the authorization code flow is supported by this authorization server.");

                return;
            }

            // Note: to support custom response modes, the OpenID Connect server middleware doesn't
            // reject unknown modes before the ApplyAuthorizationResponse event is invoked.
            // To ensure invalid modes are rejected early enough, a check is made here.
            if (!context.Request.ResponseMode.IsNullOrWhiteSpace() && !context.Request.IsFormPostResponseMode() &&
                !context.Request.IsFragmentResponseMode() && !context.Request.IsQueryResponseMode())
            {
                context.Reject(
                    error: OpenIdConnectConstants.Errors.InvalidRequest,
                    description: "The specified 'response_mode' is unsupported.");

                return;
            }

            // Retrieve the application details corresponding to the requested client_id.
            var rockContext       = new RockContext();
            var authClientService = new AuthClientService(rockContext);
            var authClient        = await authClientService.GetByClientIdAsync(context.ClientId);

            if (authClient == null)
            {
                context.Reject(
                    error: OpenIdConnectConstants.Errors.InvalidClient,
                    description: "The specified client identifier is invalid.");

                return;
            }

            if (!context.RedirectUri.IsNullOrWhiteSpace() &&
                !string.Equals(context.RedirectUri, authClient.RedirectUri, StringComparison.OrdinalIgnoreCase))
            {
                context.Reject(
                    error: OpenIdConnectConstants.Errors.InvalidClient,
                    description: "The specified 'redirect_uri' is invalid.");

                return;
            }

            context.Validate(authClient.RedirectUri);
        }
Example #2
0
        /// <summary>
        /// Gets the authentication client.
        /// </summary>
        /// <returns></returns>
        private async Task <AuthClient> GetAuthClient()
        {
            if (_authClient == null)
            {
                var rockContext       = new RockContext();
                var authClientService = new AuthClientService(rockContext);
                var authClientId      = PageParameter(PageParamKey.ClientId);
                _authClient = await authClientService.GetByClientIdAsync(authClientId);
            }

            return(_authClient);
        }
Example #3
0
        /// <summary>
        /// Gets the authentication client.
        /// </summary>
        /// <returns></returns>
        private AuthClient GetAuthClient()
        {
            if (_authClient == null)
            {
                var rockContext       = new RockContext();
                var authClientService = new AuthClientService(rockContext);
                var authClientId      = PageParameter(PageParamKey.ClientId);
                _authClient = authClientService.GetByClientId(authClientId);
            }

            return(_authClient);
        }
Example #4
0
        private void AcceptAuthorization()
        {
            var owinContext = Context.GetOwinContext();
            var request     = owinContext.GetOpenIdConnectRequest();

            // TODO: only allow valid scopes.
            var requestedScopes = request.GetScopes();
            var authClientId    = PageParameter(PageParamKey.ClientId);
            IDictionary <string, string> clientAllowedClaims = null;

            AuthClient           authClient          = null;
            IEnumerable <string> clientAllowedScopes = null;

            using (var rockContext = new RockContext())
            {
                var authClientService = new AuthClientService(rockContext);
                authClient          = authClientService.GetByClientId(authClientId);
                clientAllowedScopes = RockIdentityHelper.NarrowRequestedScopesToApprovedScopes(rockContext, authClientId, requestedScopes);
                clientAllowedClaims = RockIdentityHelper.GetAllowedClientClaims(rockContext, authClientId, clientAllowedScopes);
            }

            if (authClient == null || clientAllowedScopes == null || clientAllowedClaims == null)
            {
                // TODO: Error
                return;
            }

            // Create a new ClaimsIdentity containing the claims that
            // will be used to create an id_token, a token or a code.
            var identity = RockIdentityHelper.GetRockClaimsIdentity(CurrentUser, clientAllowedClaims, authClientId);

            // Create a new authentication ticket holding the user identity.
            var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());

            // We should set the scopes to the requested valid scopes.
            ticket.SetScopes(requestedScopes);

            // Set the resource servers the access token should be issued for.
            ticket.SetResources("resource_server");

            // Returning a SignInResult will ask ASOS to serialize the specified identity
            // to build appropriate tokens. You should always make sure the identities
            // you return contain the OpenIdConnectConstants.Claims.Subject claim. In this sample,
            // the identity always contains the name identifier returned by the external provider.

            owinContext.Authentication.SignIn(ticket.Properties, identity);
        }
Example #5
0
        /// <summary>
        /// Represents an event called for each request to the token endpoint
        /// to determine if the request is valid and should continue.
        /// </summary>
        /// <param name="context">The context instance associated with this event.</param>
        public override async Task ValidateTokenRequest(ValidateTokenRequestContext context)
        {
            // Note: the OpenID Connect server middleware supports authorization code, refresh token, client credentials
            // and resource owner password credentials grant types but this authorization provider uses a safer policy
            // rejecting the last two ones. You may consider relaxing it to support the ROPC or client credentials grant types.
            if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsRefreshTokenGrantType() && !context.Request.IsTokenRequest())
            {
                context.Reject(
                    error: OpenIdConnectConstants.Errors.UnsupportedGrantType,
                    description: "Only authorization code and refresh token grant types " +
                    "are accepted by this authorization server.");

                return;
            }

            // Note: client authentication is not mandatory for non-confidential client applications like mobile apps
            // (except when using the client credentials grant type) but this authorization server uses a safer policy
            // that makes client authentication mandatory and returns an error if client_id or client_secret is missing.
            // You may consider relaxing it to support the resource owner password credentials grant type
            // with JavaScript or desktop applications, where client credentials cannot be safely stored.
            // In this case, call context.Skip() to inform the server middleware the client is not trusted.
            if (context.ClientId.IsNullOrWhiteSpace() || context.ClientSecret.IsNullOrWhiteSpace())
            {
                context.Reject(
                    error: OpenIdConnectConstants.Errors.InvalidRequest,
                    description: "The mandatory 'client_id'/'client_secret' parameters are missing.");

                return;
            }

            // Retrieve the application details corresponding to the requested client_id.
            var rockContext       = new RockContext();
            var authClientService = new AuthClientService(rockContext);
            var authClient        = await authClientService.GetByClientIdAndSecretAsync(context.ClientId, context.ClientSecret);

            if (authClient == null)
            {
                context.Reject(
                    error: OpenIdConnectConstants.Errors.InvalidClient,
                    description: "The specified client credentials are invalid.");

                return;
            }

            context.Validate();
        }
Example #6
0
        /// <summary>
        /// Shows the detail.
        /// </summary>
        /// <param name="clientId">The rest user identifier.</param>
        public void ShowDetail(int clientId)
        {
            var rockContext = new RockContext();

            AuthClient authClient = null;
            var        isNew      = clientId.Equals(0);

            if (!isNew)
            {
                authClient          = new AuthClientService(rockContext).Get(clientId);
                lTitle.Text         = ActionTitle.Edit("Client").FormatAsHtmlTitle();
                tbClientSecret.Text = CLIENT_SECRET_PLACE_HOLDER;
            }
            else
            {
                lTitle.Text         = ActionTitle.Add("Client").FormatAsHtmlTitle();
                tbClientSecret.Text = string.Empty;
            }

            if (authClient == null)
            {
                if (!isNew)
                {
                    DisplayErrorMessage("The Auth Client with the specified Id was found.");
                    return;
                }

                authClient = new AuthClient {
                    Id = 0, IsActive = true
                };
            }

            hfRestUserId.Value = authClient.Id.ToString();

            tbName.Text                  = authClient.Name;
            cbActive.Checked             = authClient.IsActive;
            tbClientId.Text              = authClient.ClientId;
            tbRedirectUri.Text           = authClient.RedirectUri;
            tbPostLogoutRedirectUri.Text = authClient.PostLogoutRedirectUri;

            SetClaimsCheckboxValues(authClient.AllowedClaims.FromJsonOrNull <List <string> >());
            var editAllowed = authClient.IsAuthorized(Authorization.EDIT, CurrentPerson);

            lbSave.Visible = editAllowed;
        }
        /// <summary>
        /// Gets the allowed client scopes.
        /// </summary>
        /// <param name="rockContext">The rock context.</param>
        /// <param name="clientId">The client identifier.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentException">
        /// </exception>
        public static IEnumerable <string> GetAllowedClientScopes(RockContext rockContext, string clientId)
        {
            if (rockContext == null)
            {
                throw new ArgumentException($"{nameof( rockContext )} cannot be null.");
            }

            if (clientId.IsNullOrWhiteSpace())
            {
                throw new ArgumentException($"{nameof( clientId )} cannot be null or empty.");
            }

            // The OpenId is required and should always be allowed.
            var emptyScopeList    = new List <string> {
            };
            var authClientService = new AuthClientService(rockContext);

            var enabledClientScopes = authClientService
                                      .Queryable()
                                      .Where(ac => ac.ClientId == clientId)
                                      .Select(ac => ac.AllowedScopes)
                                      .FirstOrDefault();

            if (enabledClientScopes.IsNullOrWhiteSpace())
            {
                return(emptyScopeList);
            }

            var parsedClientScopes = enabledClientScopes.FromJsonOrNull <List <string> >();

            if (parsedClientScopes == null)
            {
                return(emptyScopeList);
            }

            var activeClientScopes = new AuthScopeService(rockContext)
                                     .Queryable()
                                     .Where(s => s.IsActive)
                                     .Select(s => s.Name);

            return(parsedClientScopes.Intersect(activeClientScopes));
        }
Example #8
0
        /// <summary>
        /// Handles the Delete event of the gUserLogins control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="RowEventArgs" /> instance containing the event data.</param>
        protected void gAuthClients_Delete(object sender, RowEventArgs e)
        {
            bool canEdit = IsUserAuthorized(Authorization.EDIT);

            if (canEdit)
            {
                using (var rockContext = new RockContext())
                {
                    var authClientService = new AuthClientService(rockContext);
                    var authScope         = authClientService.Get(e.RowKeyId);
                    if (authScope != null)
                    {
                        authClientService.Delete(authScope);
                        rockContext.SaveChanges();
                    }
                }
            }

            BindGrid();
        }
        /// <summary>
        /// Represents an event called for each request to the logout endpoint
        /// to determine if the request is valid and should continue.
        /// </summary>
        /// <param name="context">The context instance associated with this event.</param>
        public override async Task ValidateLogoutRequest(ValidateLogoutRequestContext context)
        {
            // When provided, post_logout_redirect_uri must exactly
            // match the address registered by the client application.
            if (!context.PostLogoutRedirectUri.IsNullOrWhiteSpace())
            {
                var rockContext       = new RockContext();
                var authClientService = new AuthClientService(rockContext);
                var authClient        = await authClientService.GetByPostLogoutRedirectUrlAsync(context.PostLogoutRedirectUri);

                if (authClient == null)
                {
                    context.Reject(
                        error: OpenIdConnectConstants.Errors.InvalidRequest,
                        description: "The specified 'post_logout_redirect_uri' is invalid.");

                    return;
                }
            }

            context.Validate();
        }
Example #10
0
        /// <summary>
        /// Gets the requested scopes.
        /// </summary>
        /// <returns></returns>
        private List <string> GetRequestedScopes()
        {
            var scopes = new List <string> {
                "Authorization"
            };
            var owinContext       = Context.GetOwinContext();
            var request           = owinContext.GetOpenIdConnectRequest();
            var requestedScopes   = request.GetScopes();
            var rockContext       = new RockContext();
            var authClientService = new AuthClientService(rockContext);

            var clientAllowedClaims = authClientService
                                      .Queryable()
                                      .Where(ac => ac.ClientId == request.ClientId)
                                      .Select(ac => ac.AllowedClaims).FirstOrDefault();

            var parsedAllowedClientClaims = clientAllowedClaims.FromJsonOrNull <List <string> >();

            if (parsedAllowedClientClaims == null)
            {
                return(new List <string>());
            }
            var authClaimService          = new AuthClaimService(rockContext);
            var activeAllowedClientClaims = authClaimService
                                            .Queryable()
                                            .Where(ac => parsedAllowedClientClaims.Contains(ac.Name))
                                            .Where(ac => ac.IsActive)
                                            .Where(ac => requestedScopes.Contains(ac.Scope.Name))
                                            .Select(ac => new { Scope = ac.Scope.PublicName, Claim = ac.PublicName })
                                            .GroupBy(ac => ac.Scope, ac => ac.Claim)
                                            .ToList()
                                            .Select(ac => new { Scope = ac.Key, Claims = string.Join(", ", ac.ToArray()) });

            scopes.AddRange(activeAllowedClientClaims.Select(ac => ac.Scope == ac.Claims ? ac.Scope : ac.Scope + " (" + ac.Claims + ")"));
            return(scopes);
            //return scopeString.SplitDelimitedValues().ToList();
        }
Example #11
0
        /// <summary>
        /// Binds the grid.
        /// </summary>
        private void BindGrid()
        {
            var rockContext       = new RockContext();
            var authClientService = new AuthClientService(rockContext);
            var authClientQuery   = authClientService.Queryable().AsNoTracking();

            if (tbName.Text.IsNotNullOrWhiteSpace())
            {
                authClientQuery = authClientQuery.Where(s => s.Name.Contains(tbName.Text));
            }

            if (ddlActiveFilter.SelectedIndex > -1)
            {
                switch (ddlActiveFilter.SelectedValue)
                {
                case "active":
                    authClientQuery = authClientQuery.Where(s => s.IsActive);
                    break;

                case "inactive":
                    authClientQuery = authClientQuery.Where(s => !s.IsActive);
                    break;
                }
            }

            // Sort
            var sortProperty = gAuthClients.SortProperty;

            if (sortProperty == null)
            {
                sortProperty = new SortProperty(new GridViewSortEventArgs("Name", SortDirection.Ascending));
            }
            authClientQuery = authClientQuery.Sort(sortProperty);

            gAuthClients.SetLinqDataSource(authClientQuery);
            gAuthClients.DataBind();
        }
        /// <summary>
        /// Gets the allowed client claims.
        /// </summary>
        /// <param name="rockContext">The rock context.</param>
        /// <param name="clientId">The client identifier.</param>
        /// <param name="allowedClientScopes">The allowed client scopes.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentException">
        /// </exception>
        public static IDictionary <string, string> GetAllowedClientClaims(RockContext rockContext, string clientId, IEnumerable <string> allowedClientScopes)
        {
            if (rockContext == null)
            {
                throw new ArgumentException($"{nameof( rockContext )} cannot be null.");
            }

            if (clientId.IsNullOrWhiteSpace())
            {
                throw new ArgumentException($"{nameof( clientId )} cannot be null or empty.");
            }

            var allowedClaimList  = new Dictionary <string, string>();
            var authClientService = new AuthClientService(rockContext);
            var allowedClaims     = authClientService.Queryable().Where(ac => ac.ClientId == clientId).Select(ac => ac.AllowedClaims).FirstOrDefault();

            if (allowedClaims.IsNullOrWhiteSpace())
            {
                return(allowedClaimList);
            }

            var parsedClaims = allowedClaims.FromJsonOrNull <List <string> >();

            if (parsedClaims == null)
            {
                return(allowedClaimList);
            }

            return(new AuthClaimService(rockContext)
                   .Queryable()
                   .Where(ac => parsedClaims.Contains(ac.Name))
                   .Where(ac => ac.IsActive)
                   .Where(ac => allowedClientScopes.Contains(ac.Scope.Name))
                   .Where(ac => ac.Scope.IsActive)
                   .ToDictionary(vc => vc.Name, vc => vc.Value));
        }
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            string clientId;
            string clientSecret;

            if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
            {
                context.TryGetFormCredentials(out clientId, out clientSecret);
            }

            if (string.IsNullOrWhiteSpace(clientId))
            {
                context.SetError("invalid_clientId", "client_id is not set.");
                await Task.FromResult<object>(null);
                return;
            }
            //TODO: get authClient (application) from db in future
            var authClient = new AuthClientService().Get(clientId);

            // auth client is null
            if (authClient == null)
            {
                context.SetError("invalid_clientId", "client_id is not valid.");
                await Task.FromResult<object>(null);
                return;
            }

            // authclient is enabled
            if (!authClient.Enabled)
            {
                context.SetError("invalid_clientId", "client_id is not valid.");
                await Task.FromResult<object>(null);
                return;
            }

            // make sure secret isn't null or empty
            if (string.IsNullOrWhiteSpace(clientSecret))
            {
                context.SetError("invalid_clientId", "Client secret should be sent.");
                await Task.FromResult<object>(null);
                return;
            }

            // make sure secret matches
            if (clientSecret != authClient.Base64Secret)
            {
                context.SetError("invalid_clientId", "Client secret is invalid.");
                await Task.FromResult<object>(null);
                return;
            }

            context.OwinContext.Set("authClient", authClient);

            context.Validated();

            await Task.FromResult<object>(null);
        }
Example #14
0
        /// <summary>
        /// Calls when a process requests authorization.
        /// </summary>
        /// <param name="actionContext">The action context, which encapsulates information for using <see cref="T:System.Web.Http.Filters.AuthorizationFilterAttribute" />.</param>
        public override void OnAuthorization(HttpActionContext actionContext)
        {
            // See if user is logged in
            var principal = System.Threading.Thread.CurrentPrincipal;

            if (principal != null && principal.Identity != null && !string.IsNullOrWhiteSpace(principal.Identity.Name))
            {
                actionContext.Request.SetUserPrincipal(principal);
                return;
            }

            // If check if ASOS authentication occurred.
            principal = actionContext.RequestContext.Principal;
            if (principal != null && principal.Identity != null)
            {
                var claimIdentity = principal.Identity as ClaimsIdentity;
                if (claimIdentity != null)
                {
                    var clientId = claimIdentity.Claims.FirstOrDefault(c => c.Type == Claims.ClientId)?.Value;
                    if (clientId.IsNotNullOrWhiteSpace())
                    {
                        using (var rockContext = new RockContext())
                        {
                            var authClientService = new AuthClientService(rockContext);
                            var authClient        = authClientService.GetByClientId(clientId);
                            if (authClient.AllowUserApiAccess)
                            {
                                var userName = claimIdentity.Claims.FirstOrDefault(c => c.Type == Claims.Username)?.Value;

                                if (userName.IsNotNullOrWhiteSpace() && clientId.IsNotNullOrWhiteSpace())
                                {
                                    UserLogin userLogin = null;

                                    var userLoginService = new UserLoginService(rockContext);
                                    userLogin = userLoginService.GetByUserName(userName);

                                    if (userLogin != null)
                                    {
                                        var identity = new GenericIdentity(userLogin.UserName);
                                        principal = new GenericPrincipal(identity, null);
                                        actionContext.Request.SetUserPrincipal(principal);
                                        return;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // If not, see if there's a valid Rock APIKey token
            TryRetrieveHeader(actionContext, HeaderTokens.AuthorizationToken, out var authToken);

            if (string.IsNullOrWhiteSpace(authToken))
            {
                string queryString = actionContext.Request.RequestUri.Query;
                authToken = System.Web.HttpUtility.ParseQueryString(queryString).Get("apikey");
            }

            if (!string.IsNullOrWhiteSpace(authToken))
            {
                var userLoginService = new UserLoginService(new Rock.Data.RockContext());
                var userLogin        = userLoginService.Queryable().Where(u => u.ApiKey == authToken).FirstOrDefault();
                if (userLogin != null)
                {
                    var identity = new GenericIdentity(userLogin.UserName);
                    principal = new GenericPrincipal(identity, null);
                    actionContext.Request.SetUserPrincipal(principal);
                    return;
                }
            }

            // If still not successful, check for a JSON Web Token
            if (TryRetrieveHeader(actionContext, HeaderTokens.JWT, out var jwtString))
            {
                // If the JSON Web Token is in the header, we can determine the User from that
                var userLogin = JwtHelper.GetUserLoginByJSONWebToken(new RockContext(), jwtString);
                if (userLogin != null)
                {
                    var identity = new GenericIdentity(userLogin.UserName);
                    principal = new GenericPrincipal(identity, null);
                    actionContext.Request.SetUserPrincipal(principal);
                    return;
                }

                // Just in rare case the GetPersonFromJWTPersonSearchKey feature is being used, see if person can be determined this way
                var person = JwtHelper.GetPersonFromJWTPersonSearchKey(jwtString);

                if (person != null)
                {
                    actionContext.Request.Properties.Add("Person", person);
                    return;
                }
            }
        }
Example #15
0
        /// <summary>
        /// Saves the authentication client.
        /// </summary>
        /// <param name="authScopeId">The authentication scope identifier.</param>
        private void SaveAuthClient(int authScopeId)
        {
            var isNew = authScopeId.Equals(0);

            var authClient = new AuthClient();

            var editAllowed = authClient.IsAuthorized(Authorization.EDIT, CurrentPerson);

            if (!editAllowed)
            {
                DisplayErrorMessage("The current user is not authorized to make changes.");
                return;
            }

            var rockContext       = new RockContext();
            var authClientService = new AuthClientService(rockContext);

            if (isNew)
            {
                authClientService.Add(authClient);
            }
            else
            {
                authClient = authClientService.Get(authScopeId);
            }

            if (authClient == null)
            {
                DisplayErrorMessage("The Auth Client with the specified Id was found.");
                return;
            }

            if (tbClientSecret.Text.IsNullOrWhiteSpace())
            {
                DisplayErrorMessage("A Client Secret is required.");
                return;
            }

            authClient.Name     = tbName.Text;
            authClient.IsActive = cbActive.Checked;
            authClient.ClientId = tbClientId.Text;

            authClient.RedirectUri           = tbRedirectUri.Text;
            authClient.PostLogoutRedirectUri = tbPostLogoutRedirectUri.Text;

            if (tbClientSecret.Text != CLIENT_SECRET_PLACE_HOLDER)
            {
                var entityTypeName        = EntityTypeCache.Get <Rock.Security.Authentication.Database>().Name;
                var databaseAuth          = AuthenticationContainer.GetComponent(entityTypeName) as Rock.Security.Authentication.Database;
                var encryptedClientSecret = databaseAuth.EncryptString(tbClientSecret.Text);
                authClient.ClientSecretHash = encryptedClientSecret;
            }

            var activeClaims   = GetActiveClaims(rockContext).Select(ac => ac.ScopeName).Distinct();
            var selectedClaims = new List <string>(activeClaims.Count());
            var selectedScopes = new List <string>(activeClaims.Count());

            foreach (var scope in activeClaims)
            {
                var checkboxList = litClaims.FindControl(scope) as RockCheckBoxList;
                if (checkboxList == null)
                {
                    continue;
                }
                var selectedScopeClaims = checkboxList.SelectedValues;
                selectedClaims.AddRange(selectedScopeClaims);
                if (selectedScopeClaims.Any())
                {
                    selectedScopes.Add(scope);
                }
            }

            authClient.AllowedClaims = selectedClaims.ToJson();
            authClient.AllowedScopes = selectedScopes.ToJson();
            rockContext.SaveChanges();
        }
Example #16
0
        /// <summary>
        /// Calls when a process requests authorization.
        /// </summary>
        /// <param name="actionContext">The action context, which encapsulates information for using <see cref="T:System.Web.Http.Filters.AuthorizationFilterAttribute" />.</param>
        public override void OnAuthorization(HttpActionContext actionContext)
        {
            // See if user is logged in
            var principal = System.Threading.Thread.CurrentPrincipal;

            if (principal != null && principal.Identity != null && !string.IsNullOrWhiteSpace(principal.Identity.Name))
            {
                actionContext.Request.SetUserPrincipal(principal);
                return;
            }

            // If check if ASOS authentication occurred.
            principal = actionContext.RequestContext.Principal;
            if (principal != null && principal.Identity != null)
            {
                var claimIdentity = principal.Identity as ClaimsIdentity;
                if (claimIdentity != null)
                {
                    var clientId = claimIdentity.Claims.FirstOrDefault(c => c.Type == Claims.ClientId)?.Value;
                    if (clientId.IsNotNullOrWhiteSpace())
                    {
                        using (var rockContext = new RockContext())
                        {
                            var authClientService = new AuthClientService(rockContext);
                            var authClient        = authClientService.GetByClientId(clientId);
                            if (authClient.AllowUserApiAccess)
                            {
                                var userName = claimIdentity.Claims.FirstOrDefault(c => c.Type == Claims.Username)?.Value;

                                if (userName.IsNotNullOrWhiteSpace() && clientId.IsNotNullOrWhiteSpace())
                                {
                                    UserLogin userLogin = null;

                                    var userLoginService = new UserLoginService(rockContext);
                                    userLogin = userLoginService.GetByUserName(userName);

                                    if (userLogin != null)
                                    {
                                        var identity = new GenericIdentity(userLogin.UserName);
                                        principal = new GenericPrincipal(identity, null);
                                        actionContext.Request.SetUserPrincipal(principal);
                                        return;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // If not, see if there's a valid token
            TryRetrieveHeader(actionContext, HeaderTokens.AuthorizationToken, out var authToken);

            if (string.IsNullOrWhiteSpace(authToken))
            {
                string queryString = actionContext.Request.RequestUri.Query;
                authToken = System.Web.HttpUtility.ParseQueryString(queryString).Get("apikey");
            }

            if (!string.IsNullOrWhiteSpace(authToken))
            {
                var userLoginService = new UserLoginService(new Rock.Data.RockContext());
                var userLogin        = userLoginService.Queryable().Where(u => u.ApiKey == authToken).FirstOrDefault();
                if (userLogin != null)
                {
                    var identity = new GenericIdentity(userLogin.UserName);
                    principal = new GenericPrincipal(identity, null);
                    actionContext.Request.SetUserPrincipal(principal);
                    return;
                }
            }

            // If still not successful, check for a JSON Web Token
            if (TryRetrieveHeader(actionContext, HeaderTokens.JWT, out var jwtString))
            {
                Person person = null;

                // We need to wait for the JwtHelper.GetPerson method rather than using the await keyword. The await keyword
                // forces this entire method to be async causing the Secured attribute to process before everything
                // is finished here
                Task.Run(async() =>
                {
                    person = await JwtHelper.GetPerson(jwtString);
                }).Wait();

                if (person != null)
                {
                    actionContext.Request.Properties.Add("Person", person);
                    return;
                }
            }
        }