You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by al...@apache.org on 2021/12/28 02:08:37 UTC

[fineract] branch develop updated: FINERACT-1012: Upgrading OAuth support to Spring Security 5.6

This is an automated email from the ASF dual-hosted git repository.

aleks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new c74bfed  FINERACT-1012: Upgrading OAuth support to Spring Security 5.6
c74bfed is described below

commit c74bfed1fe4d3f7b024005e3fe30e30b455d1475
Author: Petri Tuomola <pe...@tuomola.org>
AuthorDate: Sun Dec 26 15:42:35 2021 +0800

    FINERACT-1012: Upgrading OAuth support to Spring Security 5.6
---
 .../{build-twofactor.yml => build-oauth2.yml}      |  16 +-
 .github/workflows/build-twofactor.yml              |   6 +-
 .github/workflows/build.yml                        |   6 +-
 .gitignore                                         |   1 +
 build.gradle                                       |   2 +-
 fineract-doc/src/docs/en/03_oauth.adoc             |  49 +++--
 fineract-provider/dependencies.gradle              |   2 +-
 .../properties/oauth/application.properties        |   3 +
 .../oauth/twofactor/application.properties         |   3 +
 .../core/boot/WebXmlOauthConfiguration.java        |  44 ----
 .../core/config/OAuth2SecurityConfig.java          | 145 +++++++++++++
 .../exceptionmapper/OAuth2ExceptionEntryPoint.java |  46 +++++
 .../security/api/UserDetailsApiResource.java       | 105 +++++-----
 .../data/FineractJwtAuthenticationToken.java       |  42 ++++
 .../InsecureTwoFactorAuthenticationFilter.java     |  27 ++-
 .../filter/TenantAwareTenantIdentifierFilter.java  |   2 +-
 .../filter/TwoFactorAuthenticationFilter.java      |  31 +--
 .../security/api/SelfUserDetailsApiResource.java   |   7 +-
 .../resources/META-INF/spring/securityContext.xml  | 167 ---------------
 .../src/main/resources/static/api-docs/apiLive.htm | 225 +--------------------
 oauth2-tests/build.gradle                          |  67 ++++++
 .../dependencies.gradle                            |  24 ++-
 .../oauth2tests/OAuth2AuthenticationTest.java      | 176 ++++++++++++++++
 settings.gradle                                    |   1 +
 24 files changed, 655 insertions(+), 542 deletions(-)

diff --git a/.github/workflows/build-twofactor.yml b/.github/workflows/build-oauth2.yml
similarity index 66%
copy from .github/workflows/build-twofactor.yml
copy to .github/workflows/build-oauth2.yml
index 9e55d9f..7b715fe 100644
--- a/.github/workflows/build-twofactor.yml
+++ b/.github/workflows/build-oauth2.yml
@@ -1,4 +1,4 @@
-name: Fineract Gradle build - twofactor
+name: Fineract Gradle build - oauth2
 on: [push, pull_request]
 
 jobs:
@@ -14,6 +14,14 @@ jobs:
                 MARIADB_ROOT_PASSWORD: mysql
             options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
 
+        mock-oauth2-server:
+            image: ghcr.io/navikt/mock-oauth2-server:0.4.0
+            ports:
+                - 9000:9000
+            env:
+                SERVER_PORT: 9000
+                JSON_CONFIG: '{ "interactiveLogin": true, "httpServer": "NettyWrapper", "tokenCallbacks": [ { "issuerId": "auth/realms/fineract", "tokenExpiry": 120, "requestMappings": [{ "requestParam": "scope", "match": "fineract", "claims": { "sub": "mifos", "scope": [ "test" ] } } ] } ] }'
+
     env:
         TZ: Asia/Kolkata
     steps:
@@ -23,9 +31,9 @@ jobs:
           path: |
             ~/.gradle/caches
             ~/.gradle/wrapper
-          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+          key: ${{ runner.os }}-gradle-oauth2-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
           restore-keys: |
-            ${{ runner.os }}-gradle-
+            ${{ runner.os }}-gradle-oauth2-
       - name: Checkout
         uses: actions/checkout@v2
       - name: Set up JDK 11
@@ -49,4 +57,4 @@ jobs:
             sudo apt-get update
             sudo apt-get install ghostscript -y
       - name: Build & Test
-        run: ./gradlew --no-daemon -q --console=plain build test --fail-fast -x :integration-tests:test -Ptwofactor=enabled
+        run: ./gradlew --no-daemon -q --console=plain build test --fail-fast -x :integration-tests:test -x :twofactor-tests:test -Psecurity=oauth
diff --git a/.github/workflows/build-twofactor.yml b/.github/workflows/build-twofactor.yml
index 9e55d9f..aac7141 100644
--- a/.github/workflows/build-twofactor.yml
+++ b/.github/workflows/build-twofactor.yml
@@ -23,9 +23,9 @@ jobs:
           path: |
             ~/.gradle/caches
             ~/.gradle/wrapper
-          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+          key: ${{ runner.os }}-gradle-twofactor-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
           restore-keys: |
-            ${{ runner.os }}-gradle-
+            ${{ runner.os }}-gradle-twofactor-
       - name: Checkout
         uses: actions/checkout@v2
       - name: Set up JDK 11
@@ -49,4 +49,4 @@ jobs:
             sudo apt-get update
             sudo apt-get install ghostscript -y
       - name: Build & Test
-        run: ./gradlew --no-daemon -q --console=plain build test --fail-fast -x :integration-tests:test -Ptwofactor=enabled
+        run: ./gradlew --no-daemon -q --console=plain build test --fail-fast -x :integration-tests:test -x :oauth2-tests:test -Ptwofactor=enabled
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 908d030..9e89ac6 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -22,9 +22,9 @@ jobs:
           path: |
             ~/.gradle/caches
             ~/.gradle/wrapper
-          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+          key: ${{ runner.os }}-gradle-basicauth-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
           restore-keys: |
-            ${{ runner.os }}-gradle-
+            ${{ runner.os }}-gradle-basicauth-
       - name: Checkout
         uses: actions/checkout@v2
       - name: Set up JDK 11
@@ -48,4 +48,4 @@ jobs:
             sudo apt-get update
             sudo apt-get install ghostscript -y
       - name: Build & Test
-        run: ./gradlew --no-daemon -q --console=plain licenseMain licenseTest check build test --fail-fast doc -x :twofactor-tests:test
+        run: ./gradlew --no-daemon -q --console=plain licenseMain licenseTest check build test --fail-fast doc -x :twofactor-tests:test -x :oauth2-test:test
diff --git a/.gitignore b/.gitignore
index b62b52d..e56ba7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@ build
 *.iml
 *.ipr
 *.iws
+*.swp
 *.DS_Store
 .idea
 .vscode
diff --git a/build.gradle b/build.gradle
index 120046e..25530a8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,6 +29,7 @@ buildscript {
                 'fineract-provider',
                 'integration-tests',
                 'twofactor-tests',
+                'oauth2-tests',
                 'fineract-client'
             ].contains(it.name)
         }
@@ -111,7 +112,6 @@ allprojects  {
             // We do not use :+ to get the latest available version available on Maven Central, as that could suddenly break things.
             // We use the Renovate Bot to automatically propose Pull Requests (PRs) when upgrades for all of these versions are available.
 
-            dependency 'org.springframework.security.oauth:spring-security-oauth2:2.5.1.RELEASE'
             dependency 'org.apache.openjpa:openjpa:3.2.0' // when upgrading, also change OpenJPA version repeated above in buildscript!
             dependency 'com.google.guava:guava:31.0.1-jre'
             dependency 'com.google.code.gson:gson:2.8.9'
diff --git a/fineract-doc/src/docs/en/03_oauth.adoc b/fineract-doc/src/docs/en/03_oauth.adoc
index fec07da..933b719 100644
--- a/fineract-doc/src/docs/en/03_oauth.adoc
+++ b/fineract-doc/src/docs/en/03_oauth.adoc
@@ -1,41 +1,66 @@
 == OAuth
 
-Fineract has (basic) OAuth (2.0?) support. Here's how to use it:
+Fineract has a (basic) OAuth2 support based on Spring Boot Security. Here's how to use it:
 
 === Build
 
 You must re-build the distribution JAR (or WAR) using the special `-Psecurity=oauth` flag:
 
 ----
-./gradlew bootJAR -Psecurity=oauth
-java -jar build/libs/fineract-provider.jar
+./gradlew bootRun -Psecurity=oauth
 ----
 
 Downloads from https://fineract.apache.org, or using e.g. the https://hub.docker.com/r/apache/fineract container image, or on https://www.fineract.dev, this will not work, because they have not been built using this flag.
 
-=== Invoke `/fineract-provider/api/oauth/token`
+Previous versions of Fineract included a built-in authorisation server for issuing OAuth tokens. However, as the spring-security-oauth2 package was deprecated and replaced by built-in OAuth support in Spring Security, this is no longer supported as part of the package. Instead, you need to run a separate OAuth authorization server (e.g. https://github.com/spring-projects/spring-authorization-server) or use a 3rd-party OAuth authorization provider (https://en.wikipedia.org/wiki/List_of_OA [...]
+
+This instruction describes how to get Fineract OAuth working with a Keycloak (http://keycloak.org) based authentication provider running in a Docker container. The steps required for other OAuth providers will be similar. 
+
+=== Set up Keycloak
+
+1. From terminal, run: 'docker run -p 9000:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:15.0.2'
+1. Go to URL 'http://localhost:9000/auth/admin' and login with admin/admin
+1. Hover your mouse over text "Master" and click on "Add realm"
+1. Enter name "fineract" for your realm
+1. Click on tab "Users" on the left, then "Add user" and create user with username "mifos" 
+1. Click on tab "Credentials" at the top, and set password to "password", turning "temporary" setting to off
+1. Click on tab "Clients" on the left, and create client with ID 'community-app'
+1. In settings tab, set 'access-type' to 'confidential' and enter 'localhost' in the valid redirect URIs. 
+1. In credentials tab, copy string in field 'secret' as this will be needed in the step to request the access token
+
+Finally we need to change Keycloak configuration so that it uses the username as a subject of the token:
+
+1. Choose client 'community-app' in the tab 'Clients'
+1. Go to tab 'Mappers' and click on 'Create'
+1. Enter 'usernameInSub' as 'Name'
+1. Choose mapper type 'User Property'
+1. Enter 'username' into the field 'Property' and 'sub' into the field 'Token Claim Name'. Choose 'String' as 'Claim JSON Type'
+
+You are now ready to test out OAuth:
+
+=== Retrieve an access token from Keycloak
 
 ----
-curl [--insecure] --location --request POST \
-'https://localhost:8443/fineract-provider/api/oauth/token' \
---header 'Fineract-Platform-TenantId: default' \
+curl --location --request POST \
+'http://localhost:9000/auth/realms/fineract/protocol/openid-connect/token' \
 --header 'Content-Type: application/x-www-form-urlencoded' \
 --data-urlencode 'username=mifos' \
 --data-urlencode 'password=password' \
 --data-urlencode 'client_id=community-app' \
 --data-urlencode 'grant_type=password' \
---data-urlencode 'client_secret=123'
+--data-urlencode 'client_secret=<enter the client secret from credentials tab>'
 ----
 
-Note that the `client_id` and `client_secret` are stored in the `oauth_client_details` table in the database.
+The reply should contain a field 'access_token'. Copy the field's value and use it in the API call below:
 
 === Invoke APIs and pass `Authorization: bearer ...` header
 
 ----
 curl --location --request GET \
-'https://localhost:8443/fineract-provider/api/v1/clients' \
+'https://localhost:8443/fineract-provider/api/v1/offices' \
 --header 'Fineract-Platform-TenantId: default' \
---header 'Authorization: bearer RzfUyQ0wEnxxq4PyFCF1J-XGFCI'
+--header 'Authorization: bearer <enter the value of the access_token field>'
+
 ----
 
-NOTE: See also https://demo.fineract.dev/fineract-provider/api-docs/apiLive.htm#authentication_oauth
\ No newline at end of file
+NOTE: See also https://demo.fineract.dev/fineract-provider/api-docs/apiLive.htm#authentication_oauth
diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle
index 3378a29..393562f 100644
--- a/fineract-provider/dependencies.gradle
+++ b/fineract-provider/dependencies.gradle
@@ -30,12 +30,12 @@ dependencies {
             'org.springframework.boot:spring-boot-starter-web',
             'org.springframework.boot:spring-boot-starter-security',
             'org.springframework.boot:spring-boot-starter-cache',
+            'org.springframework.boot:spring-boot-starter-oauth2-resource-server',
 
             'org.glassfish.jersey.media:jersey-media-multipart:2.35',
 
             'org.springframework:spring-jms',
             'org.springframework:spring-context-support',
-            'org.springframework.security.oauth:spring-security-oauth2',
 
             'com.google.guava:guava',
             'com.google.code.gson:gson',
diff --git a/fineract-provider/properties/oauth/application.properties b/fineract-provider/properties/oauth/application.properties
index 057b361..6936db5 100644
--- a/fineract-provider/properties/oauth/application.properties
+++ b/fineract-provider/properties/oauth/application.properties
@@ -32,3 +32,6 @@ management.endpoints.web.exposure.include=health,info
 
 # FINERACT-914
 server.forward-headers-strategy=framework
+
+# OAuth authorisation server endpoint
+spring.security.oauth2.resourceserver.jwt.issuer-uri: http://localhost:9000/auth/realms/fineract
diff --git a/fineract-provider/properties/oauth/twofactor/application.properties b/fineract-provider/properties/oauth/twofactor/application.properties
index dc84c31..3a2e01d 100644
--- a/fineract-provider/properties/oauth/twofactor/application.properties
+++ b/fineract-provider/properties/oauth/twofactor/application.properties
@@ -32,3 +32,6 @@ management.endpoints.web.exposure.include=health,info
 
 # FINERACT-914
 server.forward-headers-strategy=framework
+
+# OAuth authorisation server endpoint
+spring.security.oauth2.resourceserver.jwt.issuer-uri: http://localhost:9000/auth/realms/fineract
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebXmlOauthConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebXmlOauthConfiguration.java
deleted file mode 100644
index 7b67579..0000000
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/WebXmlOauthConfiguration.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.infrastructure.core.boot;
-
-import org.springframework.boot.web.servlet.ServletRegistrationBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
-import org.springframework.web.servlet.DispatcherServlet;
-
-/**
- * This Configuration replaces what formerly was in web.xml. Beans are loaded only when "oauth" Profile is enabled.
- *
- * @see <a href=
- *      "http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-convert-an-existing-application-to-spring-boot">#howto-convert-an-existing-application-to-spring-boot</a>
- */
-@Configuration
-@Profile("oauth")
-public class WebXmlOauthConfiguration {
-
-    @Bean
-    public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) {
-        ServletRegistrationBean<DispatcherServlet> registrationBean = new ServletRegistrationBean<DispatcherServlet>(dispatcherServlet);
-        registrationBean.addUrlMappings("/api/oauth/token");
-        return registrationBean;
-    }
-
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OAuth2SecurityConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OAuth2SecurityConfig.java
new file mode 100644
index 0000000..a2226c9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OAuth2SecurityConfig.java
@@ -0,0 +1,145 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fineract.infrastructure.core.config;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.apache.fineract.infrastructure.core.exceptionmapper.OAuth2ExceptionEntryPoint;
+import org.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken;
+import org.apache.fineract.infrastructure.security.filter.TenantAwareTenantIdentifierFilter;
+import org.apache.fineract.infrastructure.security.filter.TwoFactorAuthenticationFilter;
+import org.apache.fineract.infrastructure.security.service.TenantAwareJpaPlatformUserDetailsService;
+import org.apache.fineract.infrastructure.security.vote.SelfServiceUserAccessVote;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.access.AccessDecisionManager;
+import org.springframework.security.access.AccessDecisionVoter;
+import org.springframework.security.access.vote.AuthenticatedVoter;
+import org.springframework.security.access.vote.RoleVoter;
+import org.springframework.security.access.vote.UnanimousBased;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
+import org.springframework.security.web.access.expression.WebExpressionVoter;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+import org.springframework.security.web.context.SecurityContextPersistenceFilter;
+
+@Configuration
+@Profile("oauth")
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+public class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter {
+
+    @Autowired
+    private TwoFactorAuthenticationFilter twoFactorAuthenticationFilter;
+
+    @Autowired
+    private TenantAwareTenantIdentifierFilter tenantAwareTenantIdentifierFilter;
+
+    @Autowired
+    private TenantAwareJpaPlatformUserDetailsService userDetailsService;
+
+    private static final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+
+        http //
+                .csrf().disable() // NOSONAR only creating a service that is used by non-browser clients
+                .antMatcher("/api/**").authorizeRequests() //
+                .antMatchers(HttpMethod.OPTIONS, "/api/**").permitAll() //
+                .antMatchers(HttpMethod.POST, "/api/*/echo").permitAll() //
+                .antMatchers(HttpMethod.POST, "/api/*/authentication").permitAll() //
+                .antMatchers(HttpMethod.POST, "/api/*/self/authentication").permitAll() //
+                .antMatchers(HttpMethod.POST, "/api/*/self/registration").permitAll() //
+                .antMatchers(HttpMethod.POST, "/api/*/self/registration/user").permitAll() //
+                .antMatchers(HttpMethod.POST, "/api/*/twofactor/validate").fullyAuthenticated() //
+                .antMatchers("/api/*/twofactor").fullyAuthenticated() //
+                .antMatchers("/api/**").access("isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')") //
+                .accessDecisionManager(accessDecisionManager()).and() //
+                .exceptionHandling().authenticationEntryPoint(new OAuth2ExceptionEntryPoint()).and()
+                .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(authenticationConverter()))
+                        .authenticationEntryPoint(new OAuth2ExceptionEntryPoint())) //
+                .sessionManagement() //
+                .sessionCreationPolicy(SessionCreationPolicy.STATELESS) //
+                .and() //
+                .addFilterAfter(tenantAwareTenantIdentifierFilter, SecurityContextPersistenceFilter.class) //
+                .addFilterAfter(twoFactorAuthenticationFilter, BasicAuthenticationFilter.class) //
+                .requiresChannel(channel -> channel.antMatchers("/api/**").requiresSecure());
+    }
+
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
+    }
+
+    private Converter<Jwt, FineractJwtAuthenticationToken> authenticationConverter() {
+        return jwt -> {
+            try {
+                UserDetails user = userDetailsService.loadUserByUsername(jwt.getSubject());
+                jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");
+                Collection<GrantedAuthority> authorities = jwtGrantedAuthoritiesConverter.convert(jwt);
+                return new FineractJwtAuthenticationToken(jwt, authorities, user);
+            } catch (UsernameNotFoundException ex) {
+                throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN), ex);
+            }
+        };
+    }
+
+    @Bean
+    public AccessDecisionManager accessDecisionManager() {
+        List<AccessDecisionVoter<? extends Object>> decisionVoters = Arrays.asList(new RoleVoter(), new AuthenticatedVoter(),
+                new WebExpressionVoter(), new SelfServiceUserAccessVote());
+
+        return new UnanimousBased(decisionVoters);
+    }
+
+    @Bean
+    public FilterRegistrationBean<TenantAwareTenantIdentifierFilter> tenantAwareTenantIdentifierFilterRegistration() throws Exception {
+        FilterRegistrationBean<TenantAwareTenantIdentifierFilter> registration = new FilterRegistrationBean<TenantAwareTenantIdentifierFilter>(
+                tenantAwareTenantIdentifierFilter);
+        registration.setEnabled(false);
+        return registration;
+    }
+
+    @Bean
+    public FilterRegistrationBean<TwoFactorAuthenticationFilter> twoFactorAuthenticationFilterRegistration() {
+        FilterRegistrationBean<TwoFactorAuthenticationFilter> registration = new FilterRegistrationBean<TwoFactorAuthenticationFilter>(
+                twoFactorAuthenticationFilter);
+        registration.setEnabled(false);
+        return registration;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/OAuth2ExceptionEntryPoint.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/OAuth2ExceptionEntryPoint.java
new file mode 100644
index 0000000..7b41234
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/OAuth2ExceptionEntryPoint.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.exceptionmapper;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+
+public class OAuth2ExceptionEntryPoint implements AuthenticationEntryPoint {
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
+            throws IOException, ServletException {
+
+        ApiGlobalErrorResponse errorResponse = ApiGlobalErrorResponse.unAuthenticated();
+        response.setContentType("application/json");
+        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            mapper.writeValue(response.getOutputStream(), errorResponse);
+        } catch (Exception e) {
+            throw new ServletException(e);
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java
index 05ce28b..64e96bf 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/api/UserDetailsApiResource.java
@@ -19,7 +19,6 @@
 package org.apache.fineract.infrastructure.security.api;
 
 import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.media.Content;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
@@ -31,24 +30,23 @@ import java.util.Set;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
 import org.apache.fineract.infrastructure.core.data.EnumOptionData;
 import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
 import org.apache.fineract.infrastructure.security.constants.TwoFactorConstants;
 import org.apache.fineract.infrastructure.security.data.AuthenticatedOauthUserData;
+import org.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken;
 import org.apache.fineract.infrastructure.security.service.SpringSecurityPlatformSecurityContext;
 import org.apache.fineract.infrastructure.security.service.TwoFactorUtils;
 import org.apache.fineract.useradministration.data.RoleData;
 import org.apache.fineract.useradministration.domain.AppUser;
 import org.apache.fineract.useradministration.domain.Role;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.Profile;
 import org.springframework.context.annotation.Scope;
-import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.stereotype.Component;
 
 /*
@@ -60,19 +58,15 @@ import org.springframework.stereotype.Component;
 @Scope("singleton")
 
 @Tag(name = "Fetch authenticated user details", description = "")
-@SuppressWarnings("deprecation") // TODO FINERACT-1012
 public class UserDetailsApiResource {
 
-    private final ResourceServerTokenServices tokenServices;
     private final ToApiJsonSerializer<AuthenticatedOauthUserData> apiJsonSerializerService;
     private final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext;
     private final TwoFactorUtils twoFactorUtils;
 
     @Autowired
-    public UserDetailsApiResource(@Qualifier("tokenServices") final ResourceServerTokenServices tokenServices,
-            final ToApiJsonSerializer<AuthenticatedOauthUserData> apiJsonSerializerService,
+    public UserDetailsApiResource(final ToApiJsonSerializer<AuthenticatedOauthUserData> apiJsonSerializerService,
             final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext, final TwoFactorUtils twoFactorUtils) {
-        this.tokenServices = tokenServices;
         this.apiJsonSerializerService = apiJsonSerializerService;
         this.springSecurityPlatformSecurityContext = springSecurityPlatformSecurityContext;
         this.twoFactorUtils = twoFactorUtils;
@@ -83,48 +77,57 @@ public class UserDetailsApiResource {
     @Operation(summary = "Fetch authenticated user details\n", description = "checks the Authentication and returns the set roles and permissions allowed.")
     @ApiResponses({
             @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = UserDetailsApiResourceSwagger.GetUserDetailsResponse.class))) })
-    public String fetchAuthenticatedUserData(
-            @QueryParam("access_token") @Parameter(description = "access_token") final String accessToken) {
-
-        final Authentication authentication = this.tokenServices.loadAuthentication(accessToken);
-        if (authentication.isAuthenticated()) {
-            final AppUser principal = (AppUser) authentication.getPrincipal();
-
-            final Collection<String> permissions = new ArrayList<>();
-            AuthenticatedOauthUserData authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), permissions);
-
-            final Collection<GrantedAuthority> authorities = new ArrayList<>(authentication.getAuthorities());
-            for (final GrantedAuthority grantedAuthority : authorities) {
-                permissions.add(grantedAuthority.getAuthority());
-            }
-
-            final Collection<RoleData> roles = new ArrayList<>();
-            final Set<Role> userRoles = principal.getRoles();
-            for (final Role role : userRoles) {
-                roles.add(role.toData());
-            }
-
-            final Long officeId = principal.getOffice().getId();
-            final String officeName = principal.getOffice().getName();
-
-            final Long staffId = principal.getStaffId();
-            final String staffDisplayName = principal.getStaffDisplayName();
-
-            final EnumOptionData organisationalRole = principal.organisationalRoleData();
-
-            final boolean requireTwoFactorAuth = twoFactorUtils.isTwoFactorAuthEnabled()
-                    && !principal.hasSpecificPermissionTo(TwoFactorConstants.BYPASS_TWO_FACTOR_PERMISSION);
-            if (this.springSecurityPlatformSecurityContext.doesPasswordHasToBeRenewed(principal)) {
-                authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), principal.getId(), accessToken,
-                        requireTwoFactorAuth);
-            } else {
-
-                authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), officeId, officeName, staffId,
-                        staffDisplayName, organisationalRole, roles, permissions, principal.getId(), accessToken, requireTwoFactorAuth);
-            }
-            return this.apiJsonSerializerService.serialize(authenticatedUserData);
+    public String fetchAuthenticatedUserData() {
+
+        final SecurityContext context = SecurityContextHolder.getContext();
+        if (context == null) {
+            return null;
+        }
+
+        final FineractJwtAuthenticationToken authentication = (FineractJwtAuthenticationToken) context.getAuthentication();
+        if (authentication == null) {
+            return null;
+        }
+
+        final AppUser principal = (AppUser) authentication.getPrincipal();
+        if (principal == null) {
+            return null;
+        }
+
+        final Collection<String> permissions = new ArrayList<>();
+        AuthenticatedOauthUserData authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), permissions);
+
+        final Collection<GrantedAuthority> authorities = new ArrayList<>(authentication.getAuthorities());
+        for (final GrantedAuthority grantedAuthority : authorities) {
+            permissions.add(grantedAuthority.getAuthority());
+        }
+
+        final Collection<RoleData> roles = new ArrayList<>();
+        final Set<Role> userRoles = principal.getRoles();
+        for (final Role role : userRoles) {
+            roles.add(role.toData());
+        }
+
+        final Long officeId = principal.getOffice().getId();
+        final String officeName = principal.getOffice().getName();
+
+        final Long staffId = principal.getStaffId();
+        final String staffDisplayName = principal.getStaffDisplayName();
+
+        final EnumOptionData organisationalRole = principal.organisationalRoleData();
+
+        final boolean requireTwoFactorAuth = twoFactorUtils.isTwoFactorAuthEnabled()
+                && !principal.hasSpecificPermissionTo(TwoFactorConstants.BYPASS_TWO_FACTOR_PERMISSION);
+
+        if (this.springSecurityPlatformSecurityContext.doesPasswordHasToBeRenewed(principal)) {
+            authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), principal.getId(),
+                    authentication.getToken().getTokenValue(), requireTwoFactorAuth);
+        } else {
+            authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), officeId, officeName, staffId, staffDisplayName,
+                    organisationalRole, roles, permissions, principal.getId(), authentication.getToken().getTokenValue(),
+                    requireTwoFactorAuth);
         }
-        return null;
 
+        return this.apiJsonSerializerService.serialize(authenticatedUserData);
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/FineractJwtAuthenticationToken.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/FineractJwtAuthenticationToken.java
new file mode 100644
index 0000000..22e1d54
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/data/FineractJwtAuthenticationToken.java
@@ -0,0 +1,42 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fineract.infrastructure.security.data;
+
+import java.util.Collection;
+import java.util.Objects;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+
+public class FineractJwtAuthenticationToken extends JwtAuthenticationToken {
+
+    private final UserDetails user;
+
+    public FineractJwtAuthenticationToken(Jwt jwt, Collection<GrantedAuthority> authorities, UserDetails user) {
+        super(jwt, authorities, user.getUsername());
+        this.user = Objects.requireNonNull(user, "user");
+    }
+
+    @Override
+    public UserDetails getPrincipal() {
+        return user;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/InsecureTwoFactorAuthenticationFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/InsecureTwoFactorAuthenticationFilter.java
index 7216f11..64e40b8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/InsecureTwoFactorAuthenticationFilter.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/InsecureTwoFactorAuthenticationFilter.java
@@ -20,12 +20,13 @@ package org.apache.fineract.infrastructure.security.filter;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
-import org.apache.fineract.useradministration.domain.AppUser;
+import org.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken;
 import org.springframework.context.annotation.Profile;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
@@ -33,6 +34,7 @@ import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.stereotype.Service;
 
 /**
@@ -58,18 +60,21 @@ public class InsecureTwoFactorAuthenticationFilter extends TwoFactorAuthenticati
         }
 
         // Add two-factor authenticated authority if user is authenticated
-        if (authentication != null && authentication.isAuthenticated() && authentication.getPrincipal() instanceof AppUser) {
-            AppUser user = (AppUser) authentication.getPrincipal();
-
-            if (user == null) {
-                return;
-            }
-
+        if (authentication != null && authentication.isAuthenticated()) {
             List<GrantedAuthority> updatedAuthorities = new ArrayList<>(authentication.getAuthorities());
             updatedAuthorities.add(new SimpleGrantedAuthority("TWOFACTOR_AUTHENTICATED"));
-            UsernamePasswordAuthenticationToken updatedAuthentication = new UsernamePasswordAuthenticationToken(
-                    authentication.getPrincipal(), authentication.getCredentials(), updatedAuthorities);
-            context.setAuthentication(updatedAuthentication);
+
+            if (authentication instanceof UsernamePasswordAuthenticationToken) {
+                UsernamePasswordAuthenticationToken updatedAuthentication = new UsernamePasswordAuthenticationToken(
+                        authentication.getPrincipal(), authentication.getCredentials(), updatedAuthorities);
+                context.setAuthentication(updatedAuthentication);
+            } else if (authentication instanceof FineractJwtAuthenticationToken) {
+                FineractJwtAuthenticationToken fineractJwtAuthenticationToken = (FineractJwtAuthenticationToken) authentication;
+                FineractJwtAuthenticationToken updatedAuthentication = new FineractJwtAuthenticationToken(
+                        fineractJwtAuthenticationToken.getToken(), (Collection<GrantedAuthority>) updatedAuthorities,
+                        (UserDetails) authentication.getPrincipal());
+                context.setAuthentication(updatedAuthentication);
+            }
         }
 
         chain.doFilter(req, res);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareTenantIdentifierFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareTenantIdentifierFilter.java
index 42a26fc..bbc23de 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareTenantIdentifierFilter.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareTenantIdentifierFilter.java
@@ -54,7 +54,7 @@ import org.springframework.web.filter.GenericFilterBean;
  *
  * Used to support Oauth2 authentication and the service is loaded only when "oauth" profile is active.
  */
-@Service(value = "tenantIdentifierProcessingFilter")
+@Service
 @Profile("oauth")
 public class TenantAwareTenantIdentifierFilter extends GenericFilterBean {
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TwoFactorAuthenticationFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TwoFactorAuthenticationFilter.java
index cc877f0..14dff4b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TwoFactorAuthenticationFilter.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TwoFactorAuthenticationFilter.java
@@ -20,6 +20,7 @@ package org.apache.fineract.infrastructure.security.filter;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
@@ -28,6 +29,7 @@ import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import org.apache.fineract.infrastructure.security.constants.TwoFactorConstants;
+import org.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken;
 import org.apache.fineract.infrastructure.security.domain.TFAccessToken;
 import org.apache.fineract.infrastructure.security.service.TwoFactorService;
 import org.apache.fineract.useradministration.domain.AppUser;
@@ -39,7 +41,7 @@ import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.oauth2.provider.OAuth2Authentication;
+import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.stereotype.Service;
 import org.springframework.web.filter.GenericFilterBean;
 
@@ -106,25 +108,28 @@ public class TwoFactorAuthenticationFilter extends GenericFilterBean {
 
             List<GrantedAuthority> updatedAuthorities = new ArrayList<>(authentication.getAuthorities());
             updatedAuthorities.add(new SimpleGrantedAuthority("TWOFACTOR_AUTHENTICATED"));
-            final Authentication updatedAuthentication = createUpdatedAuthentication(authentication, updatedAuthorities);
-            context.setAuthentication(updatedAuthentication);
+            context.setAuthentication(createUpdatedAuthentication(authentication, updatedAuthorities));
         }
 
         chain.doFilter(req, res);
     }
 
-    @SuppressWarnings("deprecation") // TODO FINERACT-1012
     private Authentication createUpdatedAuthentication(final Authentication currentAuthentication,
-            final List<GrantedAuthority> updatedAuthorities) {
-
-        final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
-                currentAuthentication.getPrincipal(), currentAuthentication.getCredentials(), updatedAuthorities);
-
-        if (currentAuthentication instanceof OAuth2Authentication) {
-            final OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) currentAuthentication;
-            return new OAuth2Authentication(oAuth2Authentication.getOAuth2Request(), authentication);
+            final List<GrantedAuthority> updatedAuthorities) throws ServletException {
+
+        if (currentAuthentication instanceof UsernamePasswordAuthenticationToken) {
+            UsernamePasswordAuthenticationToken updatedAuthentication = new UsernamePasswordAuthenticationToken(
+                    currentAuthentication.getPrincipal(), currentAuthentication.getCredentials(), updatedAuthorities);
+            return updatedAuthentication;
+        } else if (currentAuthentication instanceof FineractJwtAuthenticationToken) {
+            FineractJwtAuthenticationToken fineractJwtAuthenticationToken = (FineractJwtAuthenticationToken) currentAuthentication;
+            FineractJwtAuthenticationToken updatedAuthentication = new FineractJwtAuthenticationToken(
+                    fineractJwtAuthenticationToken.getToken(), (Collection<GrantedAuthority>) updatedAuthorities,
+                    (UserDetails) currentAuthentication.getPrincipal());
+            return updatedAuthentication;
+        } else {
+            throw new ServletException("Unknown authentication type: " + currentAuthentication.getClass().getName());
         }
 
-        return authentication;
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfUserDetailsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfUserDetailsApiResource.java
index dda8715..2104cdb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfUserDetailsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfUserDetailsApiResource.java
@@ -19,7 +19,6 @@
 package org.apache.fineract.portfolio.self.security.api;
 
 import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.media.Content;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
@@ -28,7 +27,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
 import org.apache.fineract.infrastructure.security.api.UserDetailsApiResource;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -57,8 +55,7 @@ public class SelfUserDetailsApiResource {
             + "For more info visit this link - https://demo.fineract.dev/fineract-provider/api-docs/apiLive.htm#selfoauth")
     @ApiResponses({
             @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SelfUserDetailsApiResourceSwagger.GetSelfUserDetailsResponse.class))) })
-    public String fetchAuthenticatedUserData(
-            @QueryParam("access_token") @Parameter(description = "äccess_token") final String accessToken) {
-        return this.userDetailsApiResource.fetchAuthenticatedUserData(accessToken);
+    public String fetchAuthenticatedUserData() {
+        return this.userDetailsApiResource.fetchAuthenticatedUserData();
     }
 }
diff --git a/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml b/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml
deleted file mode 100644
index 255db23..0000000
--- a/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml
+++ /dev/null
@@ -1,167 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements. See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership. The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License. You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied. See the License for the
-    specific language governing permissions and limitations
-    under the License.
-
--->
-
-<beans:beans xmlns="http://www.springframework.org/schema/security"
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
-    xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
-    xsi:schemaLocation="
-   http://www.springframework.org/schema/beans
-   https://www.springframework.org/schema/beans/spring-beans.xsd
-   http://www.springframework.org/schema/security/oauth2
-   https://www.springframework.org/schema/security/spring-security-oauth2.xsd
-   http://www.springframework.org/schema/security
-   https://www.springframework.org/schema/security/spring-security.xsd">
-
-    <beans:beans profile="oauth">
-        <http create-session="stateless" use-expressions="true" pattern="/api/v1/**"
-            entry-point-ref="oauthAuthenticationEntryPoint"
-            access-decision-manager-ref="accessDecisionManager">
-            <csrf disabled="true"/>
-            <anonymous enabled="false" />
-            <intercept-url pattern="/api//v1/**" method="OPTIONS"
-                    access="permitAll" requires-channel="https" />
-            <intercept-url pattern="/api/*/twofactor" access="isFullyAuthenticated()"
-                           method="GET" requires-channel="https" />
-            <intercept-url pattern="/api/*/twofactor" access="isFullyAuthenticated()"
-                           method="POST" requires-channel="https" />
-            <intercept-url pattern="/api/*/twofactor/validate" access="isFullyAuthenticated()"
-                           method="POST" requires-channel="https" />
-            <intercept-url pattern="/api/v1/**" access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')"
-                method="GET" requires-channel="https" />
-            <intercept-url pattern="/api/v1/**" access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')"
-                method="POST" requires-channel="https" />
-            <intercept-url pattern="/api/v1/**" access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')"
-                method="PUT" requires-channel="https" />
-            <intercept-url pattern="/api/v1/**" access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')"
-                method="DELETE" requires-channel="https" />
-            <intercept-url pattern="/api/v1/**" access="isFullyAuthenticated() and hasAuthority('TWOFACTOR_AUTHENTICATED')"
-                method="HEAD" requires-channel="https" />
-            <custom-filter ref="tenantIdentifierProcessingFilter"
-                position="FIRST" />
-            <custom-filter before="PRE_AUTH_FILTER" ref="resourceServerFilter" />
-            <custom-filter ref="twoFactorAuthFilter" after="BASIC_AUTH_FILTER" />
-            <access-denied-handler ref="oauthAccessDeniedHandler" />
-        </http>
-
-        <http pattern="/api/oauth/token" create-session="stateless"
-            entry-point-ref="oauthAuthenticationEntryPoint" use-expressions="true"
-            authentication-manager-ref="clientAuthenticationManager">
-            <csrf disabled="true"/>
-            <intercept-url pattern="/api/oauth/token" method="OPTIONS"
-                access="permitAll" requires-channel="https" />
-            <intercept-url pattern="/api/oauth/token" method="POST"
-                access="isFullyAuthenticated()" requires-channel="https" />
-            <anonymous enabled="false" />
-
-            <custom-filter ref="tenantIdentifierProcessingFilter"
-                position="FIRST" />
-            <custom-filter ref="clientCredentialsTokenEndpointFilter"
-                before="BASIC_AUTH_FILTER" />
-            <access-denied-handler ref="oauthAccessDeniedHandler" />
-        </http>
-
-        <beans:bean id="oauthAuthenticationEntryPoint"
-            class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
-            <beans:property name="realmName" value="Fineract Platform API" />
-        </beans:bean>
-
-        <beans:bean id="oauthAccessDeniedHandler"
-            class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />
-
-        <beans:bean id="clientCredentialsTokenEndpointFilter"
-            class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
-            <beans:constructor-arg value="/api/oauth/token" />
-            <beans:property name="authenticationManager" ref="clientAuthenticationManager" />
-        </beans:bean>
-
-        <beans:bean id="accessDecisionManager"
-            class="org.springframework.security.access.vote.UnanimousBased">
-            <beans:constructor-arg>
-                <beans:list>
-                    <beans:bean
-                        class="org.springframework.security.oauth2.provider.vote.ScopeVoter" />
-                    <beans:bean class="org.springframework.security.access.vote.RoleVoter" />
-                    <beans:bean
-                        class="org.springframework.security.access.vote.AuthenticatedVoter" />
-                    <beans:bean
-                        class="org.springframework.security.web.access.expression.WebExpressionVoter" />
-                    <beans:bean
-                        class="org.apache.fineract.infrastructure.security.vote.SelfServiceUserAccessVote" />
-                </beans:list>
-            </beans:constructor-arg>
-        </beans:bean>
-
-        <authentication-manager id="clientAuthenticationManager" erase-credentials="false">
-            <authentication-provider user-service-ref="clientDetailsUserService" />
-            <authentication-provider ref="customAuthenticationProvider" />
-        </authentication-manager>
-
-        <beans:bean id="clientDetailsUserService"
-            class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
-            <beans:constructor-arg ref="clientDetailsService" />
-        </beans:bean>
-
-        <beans:bean id="clientDetailsService"
-            class="org.springframework.security.oauth2.provider.client.JdbcClientDetailsService">
-            <beans:constructor-arg ref="routingDataSource" />
-        </beans:bean>
-
-        <beans:bean id="tokenStore"
-            class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
-            <beans:constructor-arg ref="routingDataSource" />
-        </beans:bean>
-
-        <beans:bean id="tokenServices"
-            class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
-            <beans:property name="tokenStore" ref="tokenStore" />
-            <beans:property name="clientDetailsService" ref="clientDetailsService" />
-            <beans:property name="supportRefreshToken" value="true" />
-            <beans:property name="refreshTokenValiditySeconds"
-                value="86400" />
-            <beans:property name="accessTokenValiditySeconds"
-                value="3600" />
-        </beans:bean>
-
-        <beans:bean id="userApprovalHandler"
-            class="org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler">
-            <beans:property name="tokenStore" ref="tokenStore" />
-            <beans:property name="clientDetailsService" ref="clientDetailsService" />
-            <beans:property name="requestFactory" ref="oAuth2RequestFactory" />
-        </beans:bean>
-
-        <beans:bean id="oAuth2RequestFactory"
-            class="org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory">
-            <beans:constructor-arg ref="clientDetailsService" />
-        </beans:bean>
-
-        <oauth:authorization-server
-            client-details-service-ref="clientDetailsService" token-services-ref="tokenServices"
-            user-approval-handler-ref="userApprovalHandler" token-endpoint-url="/api/oauth/token"
-            authorization-endpoint-url="/api/oauth/authorize">
-            <oauth:refresh-token />
-            <oauth:password />
-        </oauth:authorization-server>
-
-        <oauth:resource-server id="resourceServerFilter" stateless="false"
-            token-services-ref="tokenServices" />
-    </beans:beans>
-</beans:beans>
diff --git a/fineract-provider/src/main/resources/static/api-docs/apiLive.htm b/fineract-provider/src/main/resources/static/api-docs/apiLive.htm
index 61c0ea0..2c8c583 100644
--- a/fineract-provider/src/main/resources/static/api-docs/apiLive.htm
+++ b/fineract-provider/src/main/resources/static/api-docs/apiLive.htm
@@ -2769,22 +2769,6 @@
 							</tr>
 							<tr>
 								<td><a href="#authenticationoauth">Authentication Oauth2</a></td>
-								<td>oauth/token</td>
-								<td><a href="#oauth">OAuth2 Access and refresh Token Request</a></td>
-								<td></td>
-								<td></td>
-								<td></td>
-							</tr>
-							<tr>
-								<td></td>
-								<td></td>
-								<td><a href="#oauth_access_token_req">OAuth2 Access Token Request from refresh token</a></td>
-								<td></td>
-								<td></td>
-								<td></td>
-							</tr>
-							<tr>
-								<td></td>
 								<td>authenticated user</td>
 								<td></td>
 								<td><a href="#userdetails_request">Fetch Authenticated user details</a></td>
@@ -4499,7 +4483,7 @@ function executeAjaxRequest(url, verbType, jsonData, basicAuthKey, successFuncti
 function getOauthToken(username, password) {
 
 	var jqxhr = $.ajax({
-		url : "/fineract-provider/api/oauth/token",
+		url : "URL of the authentication server",
 		type : 'POST',
 		dataType : 'json',
 		data : {
@@ -39103,174 +39087,6 @@ No Request Body
 				<div class="method-description">
 					<h3>Authentication Oauth2</h3>
 					<p>An API capability that allows client applications to fetch current user details details using Oauth2.</p>
-					<table class=matrixHeading>
-						<tr class="matrixHeadingBG">
-							<td><div class="fineractHeading2">Field Descriptions</div></td>
-						</tr>
-						<tr class=alt>
-							<td>accessToken</td>
-						</tr>
-						<tr>
-							<td class=fielddesc>HTTP Auth bearer key. See <a
-								href="#authentication_overview">Authentication Overview</a> for
-								an example of its use.
-							</td>
-						</tr>
-						</tr>
-					</table>
-				</div>
-			</div>
-
-			<a id="oauth" name="oauth" class="old-syle-anchor">&nbsp;</a>
-			<div class="method-section">
-				<div class="method-description">
-					<h3>Authentication via OAuth2</h3>
-					<p>API for requesting OAuth2 access token and refresh token.</p>
-					<table class=matrixHeading>
-						<tr class="matrixHeadingBG">
-							<td><div class="fineractHeading2">Field Descriptions</div></td>
-						</tr>
-						<tr class=alt>
-							<td>grant_type</td>
-						</tr>
-						<tr>
-							<td class=fielddesc>Mandatory field.
-							Indicates the requested grant type. Supported values:
-							<span>password</span>.
-							</td>
-						</tr>
-						<tr class=alt>
-							<td>client_id</td>
-						</tr>
-						<tr>
-							<td class=fielddesc>Mandatory field.
-							Indicates the client application identity.
-							</td>
-						</tr>
-						<tr class=alt>
-							<td>client_secret</td>
-						</tr>
-						<tr>
-							<td class=fielddesc>Optional field.
-							Indicates the client application password.
-							</td>
-						</tr>
-						<tr class=alt>
-							<td>username</td>
-						</tr>
-						<tr>
-							<td class=fielddesc>Mandatory field.
-							Application User(resource) login name.
-							</td>
-						</tr>
-						<tr class=alt>
-							<td>password</td>
-						</tr>
-						<tr>
-							<td class=fielddesc>Mandatory field.
-							Application User(resource) password.
-							</td>
-						</tr>
-					</table>
-				</div>
-			</div>
-
-			<a id="oauth_request" name="oauth_request" class="old-syle-anchor">&nbsp;</a>
-			<div class="method-section">
-				<div class="method-description">
-					<h4>OAuth2 Refresh and Access Token Request</h4>
-				</div>
-				<div class="method-example">
-					<code class="method-declaration">
-POST https://DomainName/api/oauth/token?username={username}&password={password}&client_id={clientId}&grant_type={grant_type}&client_secret={client_secret}
-					</code>
-					<code class="method-request">
-POST api/oauth/token?username=mifos&password=password&client_id=community-app&grant_type=password&client_secret=123
-Content-Type: application/json
-No Request Body
-					</code>
-					<code class="method-response">
-{
-	""access_token": "b771987f-82fc-45ba-b521-bfe280c4e603",
-	"token_type": "bearer",
-	"refresh_token":"a2a89b23-8d22-4d90-8585-8f464db433b0",
-	"expires_in": 3599,
-	"scope": "all"
-}
-					</code>
-				</div>
-			</div>
-
-
-			<a id="oauth_access_token_req" name="oauth_access_token_req" class="old-syle-anchor">&nbsp;</a>
-			<div class="method-section">
-				<div class="method-description">
-					<h3>Authentication via OAuth2</h3>
-					<p>API for requesting OAuth2 access tokens through oAuth2 refresh tokens.</p>
-					<table class=matrixHeading>
-						<tr class="matrixHeadingBG">
-							<td><div class="fineractHeading2">Field Descriptions</div></td>
-						</tr>
-						<tr class=alt>
-							<td>grant_type</td>
-						</tr>
-						<tr>
-							<td class=fielddesc>Mandatory field.
-							Indicates the requested grant type. Supported values:
-							<span>refresh_token</span>.
-							</td>
-						</tr>
-						<tr class=alt>
-							<td>client_id</td>
-						</tr>
-						<tr>
-							<td class=fielddesc>Mandatory field.
-							Indicates the client application identity.
-							</td>
-						</tr>
-						<tr class=alt>
-							<td>client_secret</td>
-						</tr>
-						<tr>
-							<td class=fielddesc>Optional field.
-							Indicates the client application password.
-							</td>
-						</tr>
-						<tr class=alt>
-							<td>refresh_token</td>
-						</tr>
-						<tr>
-							<td class=fielddesc>Mandatory field.
-							Application refresh token to generate access token.
-							</td>
-						</tr>
-					</table>
-				</div>
-			</div>
-
-			<a id="oauth_request_with_refresh" name="oauth_request_with_refresh" class="old-syle-anchor">&nbsp;</a>
-			<div class="method-section">
-				<div class="method-description">
-					<h4>OAuth2 Token Request through Refresh Token</h4>
-				</div>
-				<div class="method-example">
-					<code class="method-declaration">
-POST https://DomainName/api/oauth/token?refresh_token={refresh_token}&client_id={clientId}&grant_type={grant_type}&client_secret={client_secret}
-					</code>
-					<code class="method-request">
-POST api/oauth/token?client_id=community-app&grant_type=refresh_token&client_secret=123&refresh_token=a2a89b23-8d22-4d90-8585-8f464db433b0
-Content-Type: application/json
-No Request Body
-					</code>
-					<code class="method-response">
-{
-	""access_token": "b771987f-82fc-45ba-b521-bfe280c4e643",
-	"token_type": "bearer",
-	"refresh_token":"a2a89b23-8d22-4d90-8585-8f464db433b0",
-	"expires_in": 3599,
-	"scope": "all"
-}
-					</code>
 				</div>
 			</div>
 
@@ -39282,10 +39098,10 @@ No Request Body
 				</div>
 				<div class="method-example">
 					<code class="method-declaration">
-POST https://DomainName/api/v1/userdetails?access_token={access_token}
+GET https://DomainName/api/v1/userdetails
 					</code>
 					<code class="method-request">
-POST userdetails?access_token=bWlmb3M6cGFzc3dvcmQ=
+GET userdetails
 Content-Type: application/json
 No Request Body
 					</code>
@@ -39342,22 +39158,6 @@ No Request Body
 
 
 
-
-					<code class="method-request">
-POST api/oauth/token?username=mifos&password=password&client_id=community-app&grant_type=password&client_secret=123
-Content-Type: application/json
-No Request Body
-					</code>
-					<code class="method-response">
-{
-    "developerMessage": "Invalid authentication details were passed in api request.",
-    "developerDocLink": "https://github.com/openMF/mifosx/wiki/HTTP-API-Error-codes",
-    "httpStatusCode": "401",
-    "defaultUserMessage": "Unauthenticated. Please login.",
-    "userMessageGlobalisationCode": "error.msg.not.authenticated",
-    "errors": []
-}
-					</code>
 				</div>
 			</div>
 
@@ -47220,10 +47020,10 @@ No Request Body
 				</div>
 				<div class="method-example">
 					<code class="method-declaration">
-POST https://DomainName/api/v1/self/userdetails?access_token={access_token}
+POST https://DomainName/api/v1/self/userdetails
 					</code>
 					<code class="method-request">
-POST self/userdetails?access_token=bWlmb3M6cGFzc3dvcmQ=
+POST self/userdetails
 Content-Type: application/json
 No Request Body
 					</code>
@@ -47258,21 +47058,6 @@ No Request Body
 }
 </code>
 
-					<code class="method-request">
-POST api/oauth/token?username=mifos&password=password&client_id=community-app&grant_type=password&client_secret=123
-Content-Type: application/json
-No Request Body
-					</code>
-					<code class="method-response">
-{
-    "developerMessage": "Invalid authentication details were passed in api request.",
-    "developerDocLink": "https://github.com/openMF/mifosx/wiki/HTTP-API-Error-codes",
-    "httpStatusCode": "401",
-    "defaultUserMessage": "Unauthenticated. Please login.",
-    "userMessageGlobalisationCode": "error.msg.not.authenticated",
-    "errors": []
-}
-					</code>
 				</div>
 			</div>
 
diff --git a/oauth2-tests/build.gradle b/oauth2-tests/build.gradle
new file mode 100644
index 0000000..8c805db
--- /dev/null
+++ b/oauth2-tests/build.gradle
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+description = 'Fineract Integration Tests for Oauth2'
+
+apply plugin: 'com.bmuschko.cargo'
+
+// Configuration for the Gradle Cargo plugin
+// https://github.com/bmuschko/gradle-cargo-plugin
+configurations {
+    tomcat
+}
+
+apply from: 'dependencies.gradle'
+
+cargo {
+    containerId "tomcat9x"
+
+    // looks like Cargo doesn't detect the WAR file automatically in the multi-module setup
+    deployable {
+        file = file("$rootDir/fineract-provider/build/libs/fineract-provider.war")
+        context = 'fineract-provider'
+    }
+
+    local {
+        installer {
+            installConfiguration = configurations.tomcat
+            downloadDir = file("$buildDir/download")
+            extractDir = file("$buildDir/tomcat")
+        }
+        startStopTimeout = 240000
+        containerProperties {
+            property 'cargo.start.jvmargs', '-Dspring.profiles.active=oauth'
+            property 'cargo.tomcat.connector.keystoreFile', file("$rootDir/fineract-provider/src/main/resources/keystore.jks")
+            property 'cargo.tomcat.connector.keystorePass', 'openmf'
+            property 'cargo.tomcat.httpSecure', true
+            property 'cargo.tomcat.connector.sslProtocol', 'TLS'
+            property 'cargo.tomcat.connector.clientAuth', false
+            property 'cargo.protocol', 'https'
+            property 'cargo.servlet.port', 8443
+        }
+    }
+}
+
+cargoRunLocal.dependsOn ':fineract-provider:war'
+cargoStartLocal.dependsOn ':fineract-provider:war'
+cargoStartLocal.mustRunAfter 'testClasses'
+
+test {
+    dependsOn cargoStartLocal
+    finalizedBy cargoStopLocal
+}
diff --git a/settings.gradle b/oauth2-tests/dependencies.gradle
similarity index 51%
copy from settings.gradle
copy to oauth2-tests/dependencies.gradle
index 596dc4e..38065ae 100644
--- a/settings.gradle
+++ b/oauth2-tests/dependencies.gradle
@@ -16,9 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-rootProject.name='fineract'
-include ':fineract-provider'
-include ':integration-tests'
-include ':twofactor-tests'
-include ':fineract-client'
-include ':fineract-doc'
+dependencies {
+    // testCompile dependencies are ONLY used in src/test, not src/main.
+    // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly!
+    //
+    tomcat 'org.apache.tomcat:tomcat:9.0.54@zip'
+    testImplementation( files("$rootDir/fineract-provider/build/classes/java/main/"),
+            project(path: ':fineract-provider', configuration: 'runtimeElements'),
+            'org.junit.jupiter:junit-jupiter-api'
+            )
+    testImplementation ('io.rest-assured:rest-assured') {
+        exclude group: 'commons-logging'
+        exclude group: 'org.apache.sling'
+        exclude group: 'com.sun.xml.bind'
+    }
+    testRuntimeOnly(
+            'org.junit.jupiter:junit-jupiter-engine'
+            )
+}
diff --git a/oauth2-tests/src/test/java/org/apache/fineract/oauth2tests/OAuth2AuthenticationTest.java b/oauth2-tests/src/test/java/org/apache/fineract/oauth2tests/OAuth2AuthenticationTest.java
new file mode 100644
index 0000000..d688de1
--- /dev/null
+++ b/oauth2-tests/src/test/java/org/apache/fineract/oauth2tests/OAuth2AuthenticationTest.java
@@ -0,0 +1,176 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.oauth2tests;
+
+import static io.restassured.RestAssured.given;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import io.restassured.RestAssured;
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.path.json.JsonPath;
+import io.restassured.response.Response;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.io.IOException;
+import javax.mail.MessagingException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class OAuth2AuthenticationTest {
+
+    private ResponseSpecification responseSpec;
+    private ResponseSpecification responseSpec403;
+    private ResponseSpecification responseSpec401;
+    private RequestSpecification requestSpec;
+    private RequestSpecification requestFormSpec;
+
+    public static final String TENANT_PARAM_NAME = "tenantIdentifier";
+    public static final String DEFAULT_TENANT = "default";
+    public static final String TENANT_IDENTIFIER = TENANT_PARAM_NAME + '=' + DEFAULT_TENANT;
+    private static final String HEALTH_URL = "/fineract-provider/actuator/health";
+
+    @BeforeEach
+    public void setup() throws InterruptedException {
+        initializeRestAssured();
+
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+
+        // Login with basic authentication
+        awaitSpringBootActuatorHealthyUp();
+
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestFormSpec = new RequestSpecBuilder().setContentType(ContentType.URLENC).build();
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.responseSpec403 = new ResponseSpecBuilder().expectStatusCode(403).build();
+        this.responseSpec401 = new ResponseSpecBuilder().expectStatusCode(401).build();
+    }
+
+    @Test
+    public void testActuatorAccess() {
+        performServerGet(requestSpec, responseSpec, "/fineract-provider/actuator/info", null);
+    }
+
+    @Test
+    public void testApiDocsAccess() {
+        performServerGet(requestSpec, responseSpec, "/fineract-provider/api-docs/apiLive.htm", null);
+    }
+
+    @Test
+    public void testAccessWithoutAuthentication() {
+        performServerGet(requestSpec, responseSpec401, "/fineract-provider/api/v1/offices/1?" + TENANT_IDENTIFIER, "");
+    }
+
+    @Test
+    public void testOAuth2Login() throws IOException, MessagingException {
+
+        performServerGet(requestSpec, responseSpec401, "/fineract-provider/api/v1/offices/1?" + TENANT_IDENTIFIER, "");
+
+        String accessToken = performServerPost(requestFormSpec, responseSpec, "http://localhost:9000/auth/realms/fineract/token",
+                "grant_type=client_credentials&client_id=community-app&client_secret=123123", "access_token");
+        assertNotNull(accessToken);
+
+        String bearerToken = performServerPost(requestFormSpec, responseSpec, "http://localhost:9000/auth/realms/fineract/token",
+                "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + accessToken
+                        + "&client_id=community-app&scope=fineract",
+                "access_token");
+        assertNotNull(bearerToken);
+
+        RequestSpecification requestSpecWithToken = new RequestSpecBuilder() //
+                .setContentType(ContentType.JSON) //
+                .addHeader("Authorization", "Bearer " + bearerToken) //
+                .build();
+
+        performServerGet(requestSpecWithToken, responseSpec, "/fineract-provider/api/v1/offices/1?" + TENANT_IDENTIFIER, "");
+    }
+
+    @Test
+    public void testGetOAuth2UserDetails() {
+        performServerGet(requestSpec, responseSpec401, "/fineract-provider/api/v1/offices/1?" + TENANT_IDENTIFIER, "");
+
+        String accessToken = performServerPost(requestFormSpec, responseSpec, "http://localhost:9000/auth/realms/fineract/token",
+                "grant_type=client_credentials&client_id=community-app&client_secret=123123", "access_token");
+        assertNotNull(accessToken);
+
+        String bearerToken = performServerPost(requestFormSpec, responseSpec, "http://localhost:9000/auth/realms/fineract/token",
+                "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + accessToken
+                        + "&client_id=community-app&scope=fineract",
+                "access_token");
+        assertNotNull(bearerToken);
+
+        RequestSpecification requestSpecWithToken = new RequestSpecBuilder() //
+                .setContentType(ContentType.JSON) //
+                .addHeader("Authorization", "Bearer " + bearerToken) //
+                .build();
+
+        Boolean authenticationCheck = performServerGet(requestSpecWithToken, responseSpec,
+                "/fineract-provider/api/v1/userdetails?" + TENANT_IDENTIFIER, "authenticated");
+        assertEquals(authenticationCheck, true);
+    }
+
+    private static void initializeRestAssured() {
+        RestAssured.baseURI = "https://localhost";
+        RestAssured.port = 8443;
+        RestAssured.keyStore("src/main/resources/keystore.jks", "openmf");
+        RestAssured.useRelaxedHTTPSValidation();
+    }
+
+    private static void awaitSpringBootActuatorHealthyUp() throws InterruptedException {
+        int attempt = 0;
+        final int max_attempts = 10;
+        Response response = null;
+
+        do {
+            try {
+                response = RestAssured.get(HEALTH_URL);
+
+                if (response.statusCode() == 200) {
+                    return;
+                }
+
+                Thread.sleep(3000);
+            } catch (Exception e) {
+                Thread.sleep(3000);
+            }
+        } while (attempt < max_attempts);
+
+        fail(HEALTH_URL + " returned " + response.prettyPrint());
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T performServerGet(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String getURL, final String jsonAttributeToGetBack) {
+        final String json = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().get(getURL).andReturn().asString();
+        if (jsonAttributeToGetBack == null) {
+            return (T) json;
+        }
+        return (T) JsonPath.from(json).get(jsonAttributeToGetBack);
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> T performServerPost(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String putURL, final String formBody, final String jsonAttributeToGetBack) {
+        final String json = given().spec(requestSpec).body(formBody).expect().spec(responseSpec).log().ifError().when().post(putURL)
+                .andReturn().asString();
+        return (T) JsonPath.from(json).get(jsonAttributeToGetBack);
+    }
+}
diff --git a/settings.gradle b/settings.gradle
index 596dc4e..69f7147 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -20,5 +20,6 @@ rootProject.name='fineract'
 include ':fineract-provider'
 include ':integration-tests'
 include ':twofactor-tests'
+include ':oauth2-tests'
 include ':fineract-client'
 include ':fineract-doc'