diff --git a/DEPENDENCIES b/DEPENDENCIES
index 3aaea84c4..9eace7eed 100644
--- a/DEPENDENCIES
+++ b/DEPENDENCIES
@@ -56,6 +56,7 @@ eventlet                                                         0.31.0
 httpagentparser                                                  1.9.1            http://www.opensource.org/licenses/mit-license.php http://shon.github.com/httpagentparser
 user-agents                                                      2.2.0            MIT                                  https://github.com/selwin/python-user-agents
 pywinpty                                                         1.1.1            Unknown                              Unknown
+authlib                                                          0.15.3           BSD                                  https://github.com/lepture/authlib
 
 NOTE: This report was generated using Python 3.9. Full information may not be
 shown for Python modules that are not required with this version.
diff --git a/docs/en_US/getting_started.rst b/docs/en_US/getting_started.rst
index b135fe082..e3d0edc96 100644
--- a/docs/en_US/getting_started.rst
+++ b/docs/en_US/getting_started.rst
@@ -37,6 +37,7 @@ Mode is pre-configured for security.
    change_user_password
    ldap
    kerberos
+   oauth2
 
 
 .. note:: Pre-compiled and configured installation packages are available for
diff --git a/docs/en_US/images/oauth2_login.png b/docs/en_US/images/oauth2_login.png
new file mode 100644
index 0000000000000000000000000000000000000000..50ecff576455b5f06af10abbd046fe0e49d8e67f
GIT binary patch
diff --git a/docs/en_US/kerberos.rst b/docs/en_US/kerberos.rst
index 1116c4672..6dcbd674c 100644
--- a/docs/en_US/kerberos.rst
+++ b/docs/en_US/kerberos.rst
@@ -34,6 +34,7 @@ from *config.py* file and modify the values for the following parameters.
    Please note that if it is not set, it will take the value of
    *default_server* parameter."
 
+
 Keytab file for HTTP Service
 ============================
 
@@ -116,3 +117,13 @@ PostgreSQL Server settings to configure Kerberos Authentication
 
 * Note that, you have to login into pgAdmin with Kerberos authentication to
   then connect to PostgreSQL using Kerberos.
+
+
+Master Password
+===============
+
+In the multi user mode, pgAdmin uses user's login password to encrypt/decrypt the PostgreSQL server password.
+In the Kerberos authentication, the pgAdmin user does not have the password, so we need an encryption key to store
+the PostgreSQL server password for the servers which are not configured to use the Kerberos authentication.
+To accomplish this, set the configuration parameter MASTER_PASSWORD to *True*, so upon setting the master password,
+it will be used as an encryption key while storing the password. If it is False, the server password can not be stored.
diff --git a/docs/en_US/oauth2.rst b/docs/en_US/oauth2.rst
new file mode 100644
index 000000000..6224b2406
--- /dev/null
+++ b/docs/en_US/oauth2.rst
@@ -0,0 +1,61 @@
+.. _oauth2:
+
+*****************************************
+`Enabling OAUTH2 Authentication`:index:
+*****************************************
+
+
+To enable OAUTH2 authentication for pgAdmin, you must configure the OAUTH2
+settings in the *config_local.py* or *config_system.py* file (see the
+:ref:`config.py <config_py>` documentation) on the system where pgAdmin is
+installed in Server mode. You can copy these settings from *config.py* file
+and modify the values for the following parameters:
+
+
+.. csv-table::
+   :header: "**Parameter**", "**Description**"
+   :class: longtable
+   :widths: 35, 55
+
+   "AUTHENTICATION_SOURCES", "The default value for this parameter is *internal*.
+   To enable OAUTH2 authentication, you must include *oauth2* in the list of values
+   for this parameter. you can modify the value as follows:
+
+   * [‘oauth2’, ‘internal’]: pgAdmin will display an additional button for authenticating with oauth2"
+    "OAUTH2_NAME", "The name of the of the oauth provider"
+    "OAUTH2_DISPLAY_NAME", "Oauth2 display name in pgAdmin"
+    "OAUTH2_CLIENT_ID", "Oauth2 client id"
+    "OAUTH2_CLIENT_SECRET", "Oauth2 secret"
+    "OAUTH2_TOKEN_URL", "Oauth2 Token URL."
+    "OAUTH2_AUTHORIZATION_URL", "Authorisation url"
+    "OAUTH2_API_BASE_URL", "Oauth2 base url"
+    "OAUTH2_USERINFO_ENDPOINT", "User Endpoint, For example: *user* (for github) and *useinfo* (for google)"
+    "OAUTH2_ICON", "The Font-awesome icon to be placed on the oauth2 button"
+    "OAUTH2_BUTTON_COLOR", "Oauth2 button color"
+    "OAUTH2_AUTO_CREATE_USER", "Set the value to *True* if you want to automatically
+    create a pgAdmin user corresponding to a successfully authenticated Oauth2 user.
+    Please note that password is not stored in the pgAdmin database."
+
+Redirect URL
+============
+
+The redirect url to configure Oauth2 server is *http://<pgAdmin Server URL>/oauth2/authorize*
+
+Master Password
+===============
+
+In the multi user mode, pgAdmin uses user's login password to encrypt/decrypt the PostgreSQL server password.
+In the Oauth2 authentication, the pgAdmin does not store the user's password, so we need an encryption key to store
+the PostgreSQL server password.
+To accomplish this, set the configuration parameter MASTER_PASSWORD to *True*, so upon setting the master password,
+it will be used as an encryption key while storing the password. If it is False, the server password can not be stored.
+
+
+Login Page
+============
+
+After configuration, on restart, you can see the login page with the Oauth2 login button(s).
+
+.. image:: images/oauth2_login.png
+    :alt: Oauth2 login
+    :align: center
diff --git a/requirements.txt b/requirements.txt
index e6022d8f1..d45566a0b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -41,3 +41,5 @@ eventlet==0.31.0
 httpagentparser==1.9.*
 user-agents==2.2.0
 pywinpty==1.1.1; sys_platform=="win32"
+Authlib==0.15.*
+requests==2.25.*
diff --git a/web/__init__.py b/web/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/web/config.py b/web/config.py
index 758af0fdf..6c9de1b22 100644
--- a/web/config.py
+++ b/web/config.py
@@ -551,10 +551,11 @@ ENHANCED_COOKIE_PROTECTION = True
 ##########################################################################
 
 # Default setting is internal
-# External Supported Sources: ldap, kerberos
+# External Supported Sources: ldap, kerberos, oauth2
 # Multiple authentication can be achieved by setting this parameter to
-# ['ldap', 'internal']. pgAdmin will authenticate the user with ldap first,
-# in case of failure internal authentication will be done.
+# ['ldap', 'internal'] or ['oauth2', 'internal'] etc.
+# pgAdmin will authenticate the user with ldap/oauth2 whatever first in the
+# list, in case of failure the second authentication option will be considered.
 
 AUTHENTICATION_SOURCES = ['internal']
 
@@ -655,6 +656,44 @@ KRB_AUTO_CREATE_USER = True
 
 KERBEROS_CCACHE_DIR = os.path.join(DATA_DIR, 'krbccache')
 
+##########################################################################
+# OAuth2 Configuration
+##########################################################################
+
+# Multiple OAUTH2 providers can be added in the list like [{...},{...}]
+# All parameters are required
+
+OAUTH2_CONFIG = [
+    {
+        # The name of the of the oauth provider, ex: github, google
+        'OAUTH2_NAME': None,
+        'OAUTH2_DISPLAY_NAME': None,
+        # Oauth client id
+        'OAUTH2_CLIENT_ID': None,
+        # Oauth secret
+        'OAUTH2_CLIENT_SECRET': None,
+        # URL to generate a token , Ex: https://github.com/login/oauth/access_token
+        'OAUTH2_TOKEN_URL': None,
+        # URL is used for authentication, Ex: https://github.com/login/oauth/authorize
+        'OAUTH2_AUTHORIZATION_URL': None,
+        # Oauth base url, ex: https://api.github.com/
+        'OAUTH2_API_BASE_URL': None,
+        # Name of the Endpoint, ex: user
+        'OAUTH2_USERINFO_ENDPOINT': None,
+        # Font-awesome icon, ex: fa-github
+        'OAUTH2_ICON': None,
+        # UI button colour, ex: #0000ff
+        'OAUTH2_BUTTON_COLOR': None,
+    }
+]
+
+# After Oauth authentication, user will be added into the SQLite database
+# automatically, if set to True.
+# Set it to False, if user should not be added automatically,
+# in this case Admin has to add the user manually in the SQLite database.
+
+OAUTH2_AUTO_CREATE_USER = True
+
 ##########################################################################
 # PSQL tool settings
 ##########################################################################
@@ -670,6 +709,7 @@ ENABLE_PSQL = False
 # In Desktop mode it is always enabled and setting is of no use.
 ##########################################################################
 ENABLE_BINARY_PATH_BROWSING = False
+
 ##########################################################################
 # Local config settings
 ##########################################################################
diff --git a/web/pgadmin/__init__.py b/web/pgadmin/__init__.py
index 41a7e328a..414ca7c1a 100644
--- a/web/pgadmin/__init__.py
+++ b/web/pgadmin/__init__.py
@@ -46,12 +46,13 @@ from pgadmin.utils.ajax import internal_server_error, make_json_response
 from pgadmin.utils.csrf import pgCSRFProtect
 from pgadmin import authenticate
 from pgadmin.utils.security_headers import SecurityHeaders
-from pgadmin.utils.constants import KERBEROS
+from pgadmin.utils.constants import KERBEROS, OAUTH2, INTERNAL, LDAP
 
 # Explicitly set the mime-types so that a corrupted windows registry will not
 # affect pgAdmin 4 to be load properly. This will avoid the issues that may
 # occur due to security fix of X_CONTENT_TYPE_OPTIONS = "nosniff".
 import mimetypes
+
 mimetypes.add_type('application/javascript', '.js')
 mimetypes.add_type('text/css', '.css')
 
@@ -467,6 +468,13 @@ def create_app(app_name=None):
         'WTF_CSRF_METHODS': ['GET', 'POST', 'PUT', 'DELETE'],
     }))
 
+    app.config.update(dict({
+        'INTERNAL': INTERNAL,
+        'LDAP': LDAP,
+        'KERBEROS': KERBEROS,
+        'OAUTH2': OAUTH2
+    }))
+
     security.init_app(app, user_datastore)
 
     # register custom unauthorised handler.
@@ -758,19 +766,18 @@ def create_app(app_name=None):
                 )
                 abort(401)
             login_user(user)
-        elif config.SERVER_MODE and\
-                app.PGADMIN_EXTERNAL_AUTH_SOURCE ==\
-                KERBEROS and \
+        elif config.SERVER_MODE and \
                 not current_user.is_authenticated and \
                 request.endpoint in ('redirects.index', 'security.login'):
-            return authenticate.login()
-
+            if app.PGADMIN_EXTERNAL_AUTH_SOURCE == KERBEROS:
+                return authenticate.login()
         # if the server is restarted the in memory key will be lost
         # but the user session may still be active. Logout the user
         # to get the key again when login
         if config.SERVER_MODE and current_user.is_authenticated and \
                 app.PGADMIN_EXTERNAL_AUTH_SOURCE != \
-                KERBEROS and \
+                KERBEROS and app.PGADMIN_EXTERNAL_AUTH_SOURCE != \
+                OAUTH2 and\
                 current_app.keyManager.get() is None and \
                 request.endpoint not in ('security.login', 'security.logout'):
             logout_user()
diff --git a/web/pgadmin/authenticate/__init__.py b/web/pgadmin/authenticate/__init__.py
index d328b0d24..e1a50e89b 100644
--- a/web/pgadmin/authenticate/__init__.py
+++ b/web/pgadmin/authenticate/__init__.py
@@ -9,68 +9,33 @@
 
 """A blueprint module implementing the Authentication."""
 
-import flask
-import pickle
+import config
+import copy
+
 from flask import current_app, flash, Response, request, url_for,\
-    render_template
+    session, redirect
 from flask_babelex import gettext
-from flask_security import current_user, login_required
-from flask_security.views import _security, _ctx
-from flask_security.utils import config_value, get_post_logout_redirect, \
-    get_post_login_redirect, logout_user
-from pgadmin.utils.ajax import make_json_response, internal_server_error
-import os
+from flask_security.views import _security
+from flask_security.utils import get_post_logout_redirect, \
+    get_post_login_redirect
 
-from flask import session
-
-import config
 from pgadmin.utils import PgAdminModule
-from pgadmin.utils.constants import KERBEROS, INTERNAL
-from pgadmin.utils.csrf import pgCSRFProtect
+from pgadmin.utils.constants import KERBEROS, INTERNAL, OAUTH2, LDAP
+from pgadmin.authenticate.registry import AuthSourceRegistry
 
-from .registry import AuthSourceRegistry
 
 MODULE_NAME = 'authenticate'
+auth_obj = None
 
 
 class AuthenticateModule(PgAdminModule):
     def get_exposed_url_endpoints(self):
-        return ['authenticate.login',
-                'authenticate.kerberos_login',
-                'authenticate.kerberos_logout',
-                'authenticate.kerberos_update_ticket',
-                'authenticate.kerberos_validate_ticket']
+        return ['authenticate.login']
 
 
 blueprint = AuthenticateModule(MODULE_NAME, __name__, static_url_path='')
 
 
-@blueprint.route("/login/kerberos",
-                 endpoint="kerberos_login", methods=["GET"])
-@pgCSRFProtect.exempt
-def kerberos_login():
-    logout_user()
-    return Response(render_template("browser/kerberos_login.html",
-                                    login_url=url_for('security.login'),
-                                    ))
-
-
-@blueprint.route("/logout/kerberos",
-                 endpoint="kerberos_logout", methods=["GET"])
-@pgCSRFProtect.exempt
-def kerberos_logout():
-    logout_user()
-    if 'KRB5CCNAME' in session:
-        # Remove the credential cache
-        cache_file_path = session['KRB5CCNAME'].split(":")[1]
-        if os.path.exists(cache_file_path):
-            os.remove(cache_file_path)
-
-    return Response(render_template("browser/kerberos_logout.html",
-                                    login_url=url_for('security.login'),
-                                    ))
-
-
 @blueprint.route('/login', endpoint='login', methods=['GET', 'POST'])
 def login():
     """
@@ -78,15 +43,18 @@ def login():
     The user input will be validated and authenticated.
     """
     form = _security.login_form()
-    auth_obj = AuthSourceManager(form, config.AUTHENTICATION_SOURCES)
-    session['_auth_source_manager_obj'] = None
 
+    auth_obj = AuthSourceManager(form, copy.deepcopy(config.AUTHENTICATION_SOURCES))
+    if OAUTH2 in config.AUTHENTICATION_SOURCES and 'oauth2_button' in request.form:
+        session['auth_obj'] = auth_obj
+
+    session['auth_source_manager'] = None
     # Validate the user
     if not auth_obj.validate():
         for field in form.errors:
             for error in form.errors[field]:
                 flash(error, 'warning')
-            return flask.redirect(get_post_logout_redirect())
+            return redirect(get_post_logout_redirect())
 
     # Authenticate the user
     status, msg = auth_obj.authenticate()
@@ -94,34 +62,40 @@ def login():
         # Login the user
         status, msg = auth_obj.login()
         current_auth_obj = auth_obj.as_dict()
+
         if not status:
             if current_auth_obj['current_source'] ==\
                     KERBEROS:
-                return flask.redirect('{0}?next={1}'.format(url_for(
+                return redirect('{0}?next={1}'.format(url_for(
                     'authenticate.kerberos_login'), url_for('browser.index')))
 
             flash(msg, 'danger')
-            return flask.redirect(get_post_logout_redirect())
-
-        session['_auth_source_manager_obj'] = current_auth_obj
-        return flask.redirect(get_post_login_redirect())
+            return redirect(get_post_logout_redirect())
+        session['auth_source_manager'] = current_auth_obj
+        if 'auth_obj' in session:
+            session['auth_obj'] = None
+        return redirect(get_post_login_redirect())
 
     elif isinstance(msg, Response):
         return msg
+    elif 'oauth2_button' in request.form:
+        return msg
     flash(msg, 'danger')
-    response = flask.redirect(get_post_logout_redirect())
+    response = redirect(get_post_logout_redirect())
     return response
 
 
-class AuthSourceManager():
+class AuthSourceManager:
     """This class will manage all the authentication sources.
      """
+
     def __init__(self, form, sources):
         self.form = form
         self.auth_sources = sources
         self.source = None
         self.source_friendly_name = INTERNAL
-        self.current_source = None
+        self.current_source = INTERNAL
+        self.update_auth_sources()
 
     def as_dict(self):
         """
@@ -135,6 +109,14 @@ class AuthSourceManager():
 
         return res
 
+    def update_auth_sources(self):
+        for auth_src in [KERBEROS, OAUTH2]:
+            if auth_src in self.auth_sources:
+                if 'internal_button' in request.form:
+                    self.auth_sources.remove(auth_src)
+                elif INTERNAL in self.auth_sources:
+                    self.auth_sources.remove(INTERNAL)
+
     def set_current_source(self, source):
         self.current_source = source
 
@@ -170,36 +152,19 @@ class AuthSourceManager():
         msg = None
         for src in self.auth_sources:
             source = get_auth_sources(src)
+            self.set_source(source)
             current_app.logger.debug(
                 "Authentication initiated via source: %s" %
                 source.get_source_name())
 
-            if self.form.data['email'] and self.form.data['password'] and \
-                    source.get_source_name() == KERBEROS:
-                msg = gettext('pgAdmin internal user authentication'
-                              ' is not enabled, please contact administrator.')
-                continue
-
             status, msg = source.authenticate(self.form)
 
-            # When server sends Unauthorized header to get the ticket over HTTP
-            # OR When kerberos authentication failed while accessing pgadmin,
-            # we need to break the loop as no need to authenticate further
-            # even if the authentication sources set to multiple
-            if not status:
-                if (hasattr(msg, 'status') and
-                    msg.status == '401 UNAUTHORIZED') or\
-                        (source.get_source_name() ==
-                         KERBEROS and
-                         request.method == 'GET'):
-                    break
-
             if status:
-                self.set_source(source)
                 self.set_current_source(source.get_source_name())
                 if msg is not None and 'username' in msg:
                     self.form._fields['email'].data = msg['username']
                 return status, msg
+
         return status, msg
 
     def login(self):
@@ -209,6 +174,13 @@ class AuthSourceManager():
             current_app.logger.debug(
                 "Authentication and Login successfully done via source : %s" %
                 self.source.get_source_name())
+
+            # Set the login, logout view as per source  if available
+            current_app.login_manager.login_view = getattr(
+                self.source, 'LOGIN_VIEW', 'security.login')
+            current_app.login_manager.logout_view = getattr(
+                self.source, 'LOGOUT_VIEW', 'security.logout')
+
         return status, msg
 
 
@@ -236,61 +208,6 @@ def init_app(app):
     auth_sources = dict()
 
     setattr(app, '_pgadmin_auth_sources', auth_sources)
-    AuthSourceRegistry.load_auth_sources()
+    AuthSourceRegistry.load_auth_sources(app)
 
     return auth_sources
-
-
-@blueprint.route("/kerberos/update_ticket",
-                 endpoint="kerberos_update_ticket", methods=["GET"])
-@pgCSRFProtect.exempt
-@login_required
-def kerberos_update_ticket():
-    """
-    Update the kerberos ticket.
-    """
-    from werkzeug.datastructures import Headers
-    headers = Headers()
-
-    authorization = request.headers.get("Authorization", None)
-
-    if authorization is None:
-        # Send the Negotiate header to the client
-        # if Kerberos ticket is not found.
-        headers.add('WWW-Authenticate', 'Negotiate')
-        return Response("Unauthorised", 401, headers)
-    else:
-        source = get_auth_sources(KERBEROS)
-        auth_header = authorization.split()
-        in_token = auth_header[1]
-
-        # Validate the Kerberos ticket
-        status, context = source.negotiate_start(in_token)
-        if status:
-            return Response("Ticket updated successfully.")
-
-        return Response(context, 500)
-
-
-@blueprint.route("/kerberos/validate_ticket",
-                 endpoint="kerberos_validate_ticket", methods=["GET"])
-@pgCSRFProtect.exempt
-@login_required
-def kerberos_validate_ticket():
-    """
-    Return the kerberos ticket lifetime left after getting the
-    ticket from the credential cache
-    """
-    import gssapi
-
-    try:
-        del_creds = gssapi.Credentials(store={'ccache': session['KRB5CCNAME']})
-        creds = del_creds.acquire(store={'ccache': session['KRB5CCNAME']})
-    except Exception as e:
-        current_app.logger.exception(e)
-        return internal_server_error(errormsg=str(e))
-
-    return make_json_response(
-        data={'ticket_lifetime': creds.lifetime},
-        status=200
-    )
diff --git a/web/pgadmin/authenticate/internal.py b/web/pgadmin/authenticate/internal.py
index 484a7fdca..e04ae55c6 100644
--- a/web/pgadmin/authenticate/internal.py
+++ b/web/pgadmin/authenticate/internal.py
@@ -31,6 +31,8 @@ class BaseAuthentication(object):
         'PASSWORD_NOT_PROVIDED': gettext('Password not provided'),
         'INVALID_EMAIL': gettext('Email/Username is not valid')
     }
+    LOGIN_VIEW = 'security.login'
+    LOGOUT_VIEW = 'security.logout'
 
     @abstractmethod
     def get_source_name(self):
diff --git a/web/pgadmin/authenticate/kerberos.py b/web/pgadmin/authenticate/kerberos.py
index 2f8fd0d6e..a454d4d16 100644
--- a/web/pgadmin/authenticate/kerberos.py
+++ b/web/pgadmin/authenticate/kerberos.py
@@ -10,22 +10,28 @@
 """A blueprint module implementing the Spnego/Kerberos authentication."""
 
 import base64
-from os import environ, path
+from os import environ, path, remove
 
-from werkzeug.datastructures import Headers
+from werkzeug.datastructures import Headers, MultiDict
 from flask_babelex import gettext
-from flask import Flask, request, Response, session,\
-    current_app, render_template, flash
+from flask import request, Response, session,\
+    current_app, render_template, flash, url_for
+from flask_security.views import _security
+from flask_security.utils import logout_user
+from flask_security import login_required
 
 import config
 from pgadmin.model import User
 from pgadmin.tools.user_management import create_user
 from pgadmin.utils.constants import KERBEROS
+from pgadmin.utils import PgAdminModule
+from pgadmin.utils.ajax import make_json_response, internal_server_error
 
-from flask_security.views import _security
-from werkzeug.datastructures import MultiDict
 
-from .internal import BaseAuthentication
+from pgadmin.authenticate.internal import BaseAuthentication
+from pgadmin.authenticate import  get_auth_sources
+from pgadmin.utils.csrf import pgCSRFProtect
+
 
 try:
     import gssapi
@@ -46,8 +52,109 @@ if config.KRB_KTNAME and config.KRB_KTNAME != '<KRB5_KEYTAB_FILE>':
     environ['KRB5_KTNAME'] = config.KRB_KTNAME
 
 
+class KerberosModule(PgAdminModule):
+    def register(self, app, options, first_registration=False):
+        # Do not look for the sub_modules,
+        # instead call blueprint.register(...) directly
+        super(PgAdminModule, self).register(app, options, first_registration)
+
+    def get_exposed_url_endpoints(self):
+        return ['kerberos.login',
+                'kerberos.logout',
+                'kerberos.update_ticket',
+                'kerberos.validate_ticket']
+
+
+def init_app(app):
+    MODULE_NAME = 'kerberos'
+
+    blueprint = KerberosModule(MODULE_NAME, __name__, static_url_path='')
+
+    @blueprint.route("/login",
+                     endpoint="login", methods=["GET"])
+    @pgCSRFProtect.exempt
+    def kerberos_login():
+        logout_user()
+        return Response(render_template("browser/kerberos_login.html",
+                                        login_url=url_for('security.login'),
+                                        ))
+
+    @blueprint.route("/logout",
+                     endpoint="logout", methods=["GET"])
+    @pgCSRFProtect.exempt
+    def kerberos_logout():
+        logout_user()
+        if 'KRB5CCNAME' in session:
+            # Remove the credential cache
+            cache_file_path = session['KRB5CCNAME'].split(":")[1]
+            if path.exists(cache_file_path):
+                remove(cache_file_path)
+
+        return Response(render_template("browser/kerberos_logout.html",
+                                        login_url=url_for('security.login'),
+                                        ))
+
+    @blueprint.route("/update_ticket",
+                     endpoint="update_ticket", methods=["GET"])
+    @pgCSRFProtect.exempt
+    @login_required
+    def kerberos_update_ticket():
+        """
+        Update the kerberos ticket.
+        """
+        from werkzeug.datastructures import Headers
+        headers = Headers()
+
+        authorization = request.headers.get("Authorization", None)
+
+        if authorization is None:
+            # Send the Negotiate header to the client
+            # if Kerberos ticket is not found.
+            headers.add('WWW-Authenticate', 'Negotiate')
+            return Response("Unauthorised", 401, headers)
+        else:
+            source = get_auth_sources(KERBEROS)
+            auth_header = authorization.split()
+            in_token = auth_header[1]
+
+            # Validate the Kerberos ticket
+            status, context = source.negotiate_start(in_token)
+            if status:
+                return Response("Ticket updated successfully.")
+
+            return Response(context, 500)
+
+    @blueprint.route("/validate_ticket",
+                     endpoint="validate_ticket", methods=["GET"])
+    @pgCSRFProtect.exempt
+    @login_required
+    def kerberos_validate_ticket():
+        """
+        Return the kerberos ticket lifetime left after getting the
+        ticket from the credential cache
+        """
+        import gssapi
+
+        try:
+            del_creds = gssapi.Credentials(store={'ccache': session['KRB5CCNAME']})
+            creds = del_creds.acquire(store={'ccache': session['KRB5CCNAME']})
+        except Exception as e:
+            current_app.logger.exception(e)
+            return internal_server_error(errormsg=str(e))
+
+        return make_json_response(
+            data={'ticket_lifetime': creds.lifetime},
+            status=200
+        )
+
+    app.register_blueprint(blueprint)
+
+
 class KerberosAuthentication(BaseAuthentication):
 
+    LOGIN_VIEW = 'kerberos.login'
+    LOGOUT_VIEW = 'kerberos.logout'
+
     def get_source_name(self):
         return KERBEROS
 
@@ -85,7 +192,7 @@ class KerberosAuthentication(BaseAuthentication):
                     if status:
                         # Saving the first 15 characters of the kerberos key
                         # to encrypt/decrypt database password
-                        session['kerberos_key'] = auth_header[1][0:15]
+                        session['pass_enc_key'] = auth_header[1][0:15]
                         # Create user
                         retval = self.__auto_create_user(
                             str(negotiate.initiator_name))
@@ -162,7 +269,7 @@ class KerberosAuthentication(BaseAuthentication):
         username = str(username)
         if config.KRB_AUTO_CREATE_USER:
             user = User.query.filter_by(
-                username=username).first()
+                username=username, auth_source=KERBEROS).first()
             if user is None:
                 return create_user({
                     'username': username,
diff --git a/web/pgadmin/authenticate/oauth2.py b/web/pgadmin/authenticate/oauth2.py
new file mode 100644
index 000000000..80ab2e11a
--- /dev/null
+++ b/web/pgadmin/authenticate/oauth2.py
@@ -0,0 +1,166 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2021, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""A blueprint module implementing the Oauth2 authentication."""
+
+import config
+
+from authlib.integrations.flask_client import OAuth
+from flask import current_app, url_for, session, Response, request,\
+    redirect, Flask, flash
+from flask_babelex import gettext
+from flask_security import login_user, current_user, login_required
+from flask_security.utils import get_post_logout_redirect, \
+    get_post_login_redirect, logout_user
+
+from pgadmin.authenticate.internal import BaseAuthentication
+from pgadmin.model import User
+from pgadmin.tools.user_management import create_user
+from pgadmin.utils.constants import OAUTH2
+from pgadmin.utils import PgAdminModule
+from pgadmin.utils.csrf import pgCSRFProtect
+from pgadmin.authenticate import  get_auth_sources
+
+from pgadmin.model import db
+
+
+class Oauth2Module(PgAdminModule):
+    def register(self, app, options, first_registration=False):
+        # Do not look for the sub_modules,
+        # instead call blueprint.register(...) directly
+        super(PgAdminModule, self).register(app, options, first_registration)
+
+    def get_exposed_url_endpoints(self):
+        return ['oauth2.authorize',
+                'oauth2.logout']
+
+
+def init_app(app):
+    MODULE_NAME = 'oauth2'
+
+    blueprint = Oauth2Module(MODULE_NAME, __name__, static_url_path='')
+
+    @blueprint.route('/authorize', endpoint="authorize",
+                     methods=['GET', 'POST'])
+    @pgCSRFProtect.exempt
+    def oauth_authorize():
+        auth_obj = session['auth_obj']
+        auth_obj.set_current_source(auth_obj.source.get_source_name())
+        status, msg = auth_obj.login()
+        if status:
+            session['auth_source_manager'] = auth_obj.as_dict()
+            session['auth_obj'] = None
+            return redirect(get_post_login_redirect())
+        logout_user()
+        flash(msg)
+        return redirect(get_post_login_redirect())
+
+    @blueprint.route('/logout', endpoint="logout",
+                     methods=['GET', 'POST'])
+    @pgCSRFProtect.exempt
+    def oauth_logout():
+        if not current_user.is_authenticated:
+            return redirect(get_post_logout_redirect())
+        for key in list(session.keys()):
+            session.pop(key)
+        logout_user()
+        return redirect(get_post_logout_redirect())
+
+    app.register_blueprint(blueprint)
+    app.login_manager.logout_view = 'oauth2.logout'
+
+
+class OAuth2Authentication(BaseAuthentication):
+    """OAuth Authentication Class"""
+
+    LOGOUT_VIEW = 'oauth2.logout'
+
+    oauth_obj = OAuth(Flask(__name__))
+    oauth2_clients = {}
+    oauth2_config = {}
+
+    def __init__(self):
+        for oauth2_config in config.OAUTH2_CONFIG:
+
+            OAuth2Authentication.oauth2_config[oauth2_config['OAUTH2_NAME']
+            ] = oauth2_config
+
+            OAuth2Authentication.oauth2_clients[oauth2_config['OAUTH2_NAME']] =\
+                OAuth2Authentication.oauth_obj.register(
+                    name=oauth2_config['OAUTH2_NAME'],
+                    client_id=oauth2_config['OAUTH2_CLIENT_ID'],
+                    client_secret=oauth2_config['OAUTH2_CLIENT_SECRET'],
+                    access_token_url=oauth2_config['OAUTH2_TOKEN_URL'],
+                    authorize_url=oauth2_config['OAUTH2_AUTHORIZATION_URL'],
+                    api_base_url=oauth2_config['OAUTH2_API_BASE_URL'],
+                    client_kwargs={'scope': 'email profile'}
+                )
+
+    def get_source_name(self):
+        return OAUTH2
+
+    def get_friendly_name(self):
+        return self.oauth2_config[self.oauth2_current_client]['OAUTH2_NAME']
+
+    def validate(self, form):
+        return True
+
+    def login(self, form):
+        profile = self.get_user_profile()
+        print(profile)
+        if 'email' not in profile or not profile['email']:
+            current_app.logger.exception(
+                'An email is required for authentication'
+            )
+            return False, gettext(
+                "An email is required for the oauth authentication.")
+
+        user, msg = self.__auto_create_user(profile)
+        if user:
+            user = db.session.query(User).filter_by(
+                username=profile['email'], auth_source=OAUTH2).first()
+            current_app.login_manager.logout_view = \
+                OAuth2Authentication.LOGOUT_VIEW
+            return login_user(user), None
+        return False, msg
+
+    def get_user_profile(self):
+        session['oauth2_token'] = self.oauth2_clients[
+            self.oauth2_current_client].authorize_access_token()
+
+        session['pass_enc_key'] = session['oauth2_token']['access_token']
+
+        resp = self.oauth2_clients[self.oauth2_current_client].get(
+            self.oauth2_config[
+                self.oauth2_current_client]['OAUTH2_USERINFO_ENDPOINT'],
+            token=session['oauth2_token']
+        )
+        resp.raise_for_status()
+        return resp.json()
+
+    def authenticate(self, form):
+        self.oauth2_current_client = request.form['oauth2_button']
+        redirect_url = url_for('oauth2.authorize', _external=True)
+        return False, self.oauth2_clients[self.oauth2_current_client
+        ].authorize_redirect(redirect_url)
+
+    def __auto_create_user(self, resp):
+        user = User.query.filter_by(username=resp['email'],
+                                    auth_source=OAUTH2).first()
+        if not user:
+            if config.OAUTH2_AUTO_CREATE_USER:
+                return create_user({
+                    'username': resp['email'],
+                    'email': resp['email'],
+                    'role': 2,
+                    'active': True,
+                    'auth_source': OAUTH2
+                })
+
+        return True, {'username': resp['email']}
diff --git a/web/pgadmin/authenticate/registry.py b/web/pgadmin/authenticate/registry.py
index 7d797e658..46f10b2db 100644
--- a/web/pgadmin/authenticate/registry.py
+++ b/web/pgadmin/authenticate/registry.py
@@ -53,7 +53,7 @@ class AuthSourceRegistry(ABCMeta):
         )
 
     @classmethod
-    def load_auth_sources(cls):
+    def load_auth_sources(cls, app):
         # Initialize the registry only if it has not yet been initialized
         if AuthSourceRegistry.registry is None:
             AuthSourceRegistry.registry = dict()
@@ -62,4 +62,6 @@ class AuthSourceRegistry(ABCMeta):
         from werkzeug.utils import find_modules
 
         for module_name in find_modules(__package__, True):
-            import_module(module_name)
+            module = import_module(module_name)
+            if hasattr(module, 'init_app'):
+                module.init_app(app)
diff --git a/web/pgadmin/authenticate/static/js/kerberos.js b/web/pgadmin/authenticate/static/js/kerberos.js
index 64373369c..588f424fe 100644
--- a/web/pgadmin/authenticate/static/js/kerberos.js
+++ b/web/pgadmin/authenticate/static/js/kerberos.js
@@ -4,7 +4,7 @@ import pgConst from 'pgadmin.browser.constants';
 
 function fetch_ticket() {
   // Fetch the Kerberos Updated ticket through SPNEGO
-  return fetch(url_for('authenticate.kerberos_update_ticket')
+  return fetch(url_for('kerberos.update_ticket')
   )
     .then(function(response){
       if (response.status >= 200 && response.status < 300) {
@@ -18,7 +18,7 @@ function fetch_ticket() {
 function fetch_ticket_lifetime () {
   // Fetch the Kerberos ticket lifetime left
 
-  return fetch(url_for('authenticate.kerberos_validate_ticket')
+  return fetch(url_for('kerberos.validate_ticket')
   )
     .then(
       function(response){
diff --git a/web/pgadmin/browser/__init__.py b/web/pgadmin/browser/__init__.py
index fa7aa9e54..d71a06e3d 100644
--- a/web/pgadmin/browser/__init__.py
+++ b/web/pgadmin/browser/__init__.py
@@ -50,7 +50,7 @@ from pgadmin.utils.master_password import validate_master_password, \
     set_crypt_key, process_masterpass_disabled
 from pgadmin.model import User
 from pgadmin.utils.constants import MIMETYPE_APP_JS, PGADMIN_NODE,\
-    INTERNAL, KERBEROS, LDAP, QT_DEFAULT_PLACEHOLDER
+    INTERNAL, KERBEROS, LDAP, QT_DEFAULT_PLACEHOLDER, OAUTH2
 from pgadmin.authenticate import AuthSourceManager
 
 try:
@@ -607,14 +607,8 @@ class BrowserPluginModule(PgAdminModule):
 
 
 def _get_logout_url():
-    if config.SERVER_MODE and\
-            session['_auth_source_manager_obj']['current_source'] == \
-            KERBEROS:
-        return '{0}?next={1}'.format(url_for(
-            'authenticate.kerberos_logout'), url_for(BROWSER_INDEX))
-
     return '{0}?next={1}'.format(
-        url_for('security.logout'), url_for(BROWSER_INDEX))
+        url_for(current_app.login_manager.logout_view), url_for(BROWSER_INDEX))
 
 
 def _get_supported_browser():
@@ -748,10 +742,10 @@ def index():
         if len(config.AUTHENTICATION_SOURCES) == 1\
                 and INTERNAL in config.AUTHENTICATION_SOURCES:
             auth_only_internal = True
-        auth_source = session['_auth_source_manager_obj'][
+        auth_source = session['auth_source_manager'][
             'source_friendly_name']
 
-        if session['_auth_source_manager_obj']['current_source'] == KERBEROS:
+        if not config.MASTER_PASSWORD_REQUIRED and 'pass_enc_key' in session:
             session['allow_save_password'] = False
 
     response = Response(render_template(
@@ -877,7 +871,8 @@ def app_constants():
         render_template('browser/js/constants.js',
                         INTERNAL=INTERNAL,
                         LDAP=LDAP,
-                        KERBEROS=KERBEROS),
+                        KERBEROS=KERBEROS,
+                        OAUTH2=OAUTH2),
         200, {'Content-Type': MIMETYPE_APP_JS}
     )
 
@@ -1005,8 +1000,9 @@ def set_master_password():
         data = json.loads(data)
 
     # Master password is not applicable for server mode
-    if not config.SERVER_MODE and config.MASTER_PASSWORD_REQUIRED:
-
+    # Enable master password if oauth is used
+    if not config.SERVER_MODE or OAUTH2 in config.AUTHENTICATION_SOURCES\
+            and config.MASTER_PASSWORD_REQUIRED:
         # if master pass is set previously
         if current_user.masterpass_check is not None and \
             data.get('button_click') and \
@@ -1043,7 +1039,7 @@ def set_master_password():
                 existing=True,
                 present=False,
             )
-        elif not get_crypt_key()[0]:
+        elif not get_crypt_key()[1]:
             error_message = None
             if data.get('button_click') and data.get('password') == '':
                 # If user attempted to enter a blank password, then throw error
@@ -1334,6 +1330,9 @@ if hasattr(config, 'SECURITY_RECOVERABLE') and config.SECURITY_RECOVERABLE:
 
                 do_flash(*get_message('PASSWORD_RESET'))
                 login_user(user)
+                auth_obj = AuthSourceManager(form, [INTERNAL])
+                session['auth_source_manager'] = auth_obj.as_dict()
+
                 return redirect(get_url(_security.post_reset_view) or
                                 get_url(_security.post_login_view))
 
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_role_dependencies_sql.py b/web/pgadmin/browser/server_groups/servers/tests/test_role_dependencies_sql.py
index fefe6a75f..c186ac4f0 100644
--- a/web/pgadmin/browser/server_groups/servers/tests/test_role_dependencies_sql.py
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_role_dependencies_sql.py
@@ -7,6 +7,7 @@
 #
 ##########################################################################
 import os
+import uuid
 
 import jinja2
 from regression.python_test_utils import test_utils
@@ -24,20 +25,21 @@ class TestRoleDependenciesSql(SQLTemplateTestBase):
     def __init__(self):
         super(TestRoleDependenciesSql, self).__init__()
         self.table_id = -1
+        self.role_name = "testpgadmin%s" % str(uuid.uuid4())[1:8]
 
     def setUp(self):
         with test_utils.Database(self.server) as (connection, database_name):
             cursor = connection.cursor()
             try:
                 cursor.execute(
-                    "CREATE ROLE testpgadmin LOGIN PASSWORD '%s'"
-                    % self.server['db_password'])
+                    "CREATE ROLE %s LOGIN PASSWORD '%s'"
+                    % (self.role_name, self.server['db_password']))
             except Exception as exception:
                 print(exception)
             connection.commit()
 
         self.server_with_modified_user = self.server.copy()
-        self.server_with_modified_user['username'] = "testpgadmin"
+        self.server_with_modified_user['username'] = self.role_name
 
     def runTest(self):
         if hasattr(self, "ignore_test"):
@@ -61,7 +63,7 @@ class TestRoleDependenciesSql(SQLTemplateTestBase):
     def tearDown(self):
         with test_utils.Database(self.server) as (connection, database_name):
             cursor = connection.cursor()
-            cursor.execute("DROP ROLE testpgadmin")
+            cursor.execute("DROP ROLE %s" % self.role_name)
             connection.commit()
 
     def generate_sql(self, version):
diff --git a/web/pgadmin/browser/templates/browser/js/constants.js b/web/pgadmin/browser/templates/browser/js/constants.js
index 6a63d6ed9..6da835b65 100644
--- a/web/pgadmin/browser/templates/browser/js/constants.js
+++ b/web/pgadmin/browser/templates/browser/js/constants.js
@@ -12,6 +12,7 @@ define('pgadmin.browser.constants', [], function() {
   return {
     'INTERNAL': '{{ INTERNAL }}',
     'LDAP': '{{ LDAP }}',
-    'KERBEROS': '{{ KERBEROS }}'
+    'KERBEROS': '{{ KERBEROS }}',
+    'OAUTH2': '{{ OAUTH2 }}'
   }
 });
diff --git a/web/pgadmin/browser/tests/test_kerberos_with_mocking.py b/web/pgadmin/browser/tests/test_kerberos_with_mocking.py
index 6b61dc1d0..aba4ed12b 100644
--- a/web/pgadmin/browser/tests/test_kerberos_with_mocking.py
+++ b/web/pgadmin/browser/tests/test_kerberos_with_mocking.py
@@ -13,6 +13,7 @@ from regression.python_test_utils import test_utils as utils
 from pgadmin.authenticate.registry import AuthSourceRegistry
 from unittest.mock import patch, MagicMock
 from werkzeug.datastructures import Headers
+from pgadmin.utils.constants import LDAP, INTERNAL, KERBEROS
 
 
 class KerberosLoginMockTestCase(BaseTestGenerator):
@@ -23,17 +24,17 @@ class KerberosLoginMockTestCase(BaseTestGenerator):
 
     scenarios = [
         ('Spnego/Kerberos Authentication: Test Unauthorized', dict(
-            auth_source=['kerberos'],
+            auth_source=[KERBEROS],
             auto_create_user=True,
             flag=1
         )),
         ('Spnego/Kerberos Authentication: Test Authorized', dict(
-            auth_source=['kerberos'],
+            auth_source=[KERBEROS],
             auto_create_user=True,
             flag=2
         )),
         ('Spnego/Kerberos Update Ticket', dict(
-            auth_source=['kerberos'],
+            auth_source=[KERBEROS],
             auto_create_user=True,
             flag=3
         ))
@@ -49,7 +50,7 @@ class KerberosLoginMockTestCase(BaseTestGenerator):
 
     def setUp(self):
         app_config.AUTHENTICATION_SOURCES = self.auth_source
-        self.app.PGADMIN_EXTERNAL_AUTH_SOURCE = 'kerberos'
+        self.app.PGADMIN_EXTERNAL_AUTH_SOURCE = KERBEROS
 
     def runTest(self):
         """This function checks spnego/kerberos login functionality."""
@@ -101,13 +102,13 @@ class KerberosLoginMockTestCase(BaseTestGenerator):
 
         del_crads = delCrads()
 
-        AuthSourceRegistry.registry['kerberos'].negotiate_start = MagicMock(
+        AuthSourceRegistry.registry[KERBEROS].negotiate_start = MagicMock(
             return_value=[True, del_crads])
         return del_crads
 
     def test_update_ticket(self):
         # Response header should include the Negotiate header in the first call
-        response = self.tester.get('/authenticate/kerberos/update_ticket')
+        response = self.tester.get('/kerberos/update_ticket')
         self.assertEqual(response.status_code, 401)
         self.assertEqual(response.headers.get('www-authenticate'), 'Negotiate')
 
@@ -117,12 +118,12 @@ class KerberosLoginMockTestCase(BaseTestGenerator):
         krb_token = Headers({})
         krb_token['Authorization'] = 'Negotiate CTOKEN'
 
-        response = self.tester.get('/authenticate/kerberos/update_ticket',
+        response = self.tester.get('/kerberos/update_ticket',
                                    headers=krb_token)
         self.assertEqual(response.status_code, 200)
 
     def tearDown(self):
-        self.app.PGADMIN_EXTERNAL_AUTH_SOURCE = 'ldap'
+        self.tester.logout()
 
     @classmethod
     def tearDownClass(cls):
@@ -130,5 +131,6 @@ class KerberosLoginMockTestCase(BaseTestGenerator):
         We need to again login the test client as soon as test scenarios
         finishes.
         """
-        app_config.AUTHENTICATION_SOURCES = ['internal']
+        cls.tester.logout()
+        app_config.AUTHENTICATION_SOURCES = [INTERNAL]
         utils.login_tester_account(cls.tester)
diff --git a/web/pgadmin/browser/tests/test_ldap_with_mocking.py b/web/pgadmin/browser/tests/test_ldap_with_mocking.py
index 30df7729e..6e69e3ce9 100644
--- a/web/pgadmin/browser/tests/test_ldap_with_mocking.py
+++ b/web/pgadmin/browser/tests/test_ldap_with_mocking.py
@@ -13,6 +13,7 @@ from regression.python_test_utils import test_utils as utils
 from regression.test_setup import config_data
 from pgadmin.authenticate.registry import AuthSourceRegistry
 from unittest.mock import patch
+from pgadmin.utils.constants import LDAP, INTERNAL
 
 
 class LDAPLoginMockTestCase(BaseTestGenerator):
@@ -23,17 +24,17 @@ class LDAPLoginMockTestCase(BaseTestGenerator):
 
     scenarios = [
         ('LDAP Authentication with Auto Create User', dict(
-            auth_source=['ldap'],
+            auth_source=[LDAP],
             auto_create_user=True,
             username='ldap_user',
             password='ldap_pass')),
         ('LDAP Authentication without Auto Create User', dict(
-            auth_source=['ldap'],
+            auth_source=[LDAP],
             auto_create_user=False,
             username='ldap_user',
             password='ldap_pass')),
         ('LDAP + Internal Authentication', dict(
-            auth_source=['ldap', 'internal'],
+            auth_source=[LDAP, INTERNAL],
             auto_create_user=False,
             username=config_data[
                 'pgAdmin4_login_credentials']['login_username'],
@@ -57,13 +58,13 @@ class LDAPLoginMockTestCase(BaseTestGenerator):
         app_config.LDAP_BIND_USER = None
         app_config.LDAP_BIND_PASSWORD = None
 
-    @patch.object(AuthSourceRegistry.registry['ldap'], 'connect',
+    @patch.object(AuthSourceRegistry.registry[LDAP], 'connect',
                   return_value=[True, "Done"])
-    @patch.object(AuthSourceRegistry.registry['ldap'], 'search_ldap_user',
+    @patch.object(AuthSourceRegistry.registry[LDAP], 'search_ldap_user',
                   return_value=[True, ''])
     def runTest(self, conn_mock_obj, search_mock_obj):
         """This function checks ldap login functionality."""
-        AuthSourceRegistry.registry['ldap'].dedicated_user = False
+        AuthSourceRegistry.registry[LDAP].dedicated_user = False
         res = self.tester.login(self.username, self.password, True)
         respdata = 'Gravatar image for %s' % self.username
         self.assertTrue(respdata in res.data.decode('utf8'))
@@ -78,5 +79,5 @@ class LDAPLoginMockTestCase(BaseTestGenerator):
         finishes.
         """
         cls.tester.logout()
-        app_config.AUTHENTICATION_SOURCES = ['internal']
+        app_config.AUTHENTICATION_SOURCES = [INTERNAL]
         utils.login_tester_account(cls.tester)
diff --git a/web/pgadmin/browser/tests/test_oauth2_with_mocking.py b/web/pgadmin/browser/tests/test_oauth2_with_mocking.py
new file mode 100644
index 000000000..5bdfe86ef
--- /dev/null
+++ b/web/pgadmin/browser/tests/test_oauth2_with_mocking.py
@@ -0,0 +1,133 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2021, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import config as app_config
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from pgadmin.authenticate.registry import AuthSourceRegistry
+from unittest.mock import patch, MagicMock
+from pgadmin.authenticate import AuthSourceManager
+from pgadmin.utils.constants import OAUTH2, LDAP, INTERNAL
+
+
+class Oauth2LoginMockTestCase(BaseTestGenerator):
+    """
+    This class checks oauth2  login functionality by mocking
+    External Oauth2 Authentication.
+    """
+
+    scenarios = [
+        ('Oauth2 External Authentication', dict(
+            auth_source=['oauth2'],
+            oauth2_provider='github',
+            flag=1
+        )),
+         ('Oauth2 Authentication', dict(
+            auth_source=['oauth2'],
+            oauth2_provider='github',
+            flag=2
+        )),
+    ]
+
+    @classmethod
+    def setUpClass(cls):
+        """
+        We need to logout the test client as we are testing
+        spnego/kerberos login scenarios.
+        """
+        cls.tester.logout()
+
+    def setUp(self):
+        app_config.AUTHENTICATION_SOURCES = self.auth_source
+        self.app.PGADMIN_EXTERNAL_AUTH_SOURCE = OAUTH2
+
+    def runTest(self):
+        """This function checks oauth2 login functionality."""
+        if app_config.SERVER_MODE is False:
+            self.skipTest(
+                "Can not run Oauth2 Authentication in the Desktop mode."
+            )
+
+        if self.flag == 1:
+            self.test_external_authentication()
+        elif self.flag == 2:
+            self.test_oauth2_authentication()
+
+    def test_external_authentication(self):
+        """
+        Ensure that the user should be redirected
+        to the external url for the authentication.
+        """
+
+        AuthSourceManager.update_auth_sources = MagicMock()
+        auth_obj = AuthSourceManager({}, [OAUTH2])
+
+        profile = self.mock_user_profile()
+        try:
+            res = self.tester.login(
+                email=None, password=None,
+                _follow_redirects=True,
+                headers=None,
+                extra_form_data=dict(oauth2_button=self.oauth2_provider)
+            )
+        except Exception as e:
+            self.assertEqual('Following external'
+                             ' redirects is not supported.', str(e))
+
+    def test_oauth2_authentication(self):
+        """
+        Ensure that when the client sends an correct authorization token,
+        they receive a 200 OK response and the user principal is extracted and
+        passed on to the routed method.
+        """
+
+        profile = self.mock_user_profile()
+
+        # Mock Oauth2 Authenticate
+        AuthSourceRegistry.registry[OAUTH2].authenticate = MagicMock(
+            return_value=[True, ''])
+
+        AuthSourceManager.update_auth_sources = MagicMock()
+
+        # Create AuthSourceManager object
+        auth_obj = AuthSourceManager({}, [OAUTH2])
+        auth_source = AuthSourceRegistry.create(OAUTH2)
+        auth_obj.set_source(auth_source)
+        auth_obj.set_current_source(auth_source.get_source_name())
+
+        # Check the login with Oauth2
+        res = self.tester.login(email=None, password=None,
+                                _follow_redirects=True,
+                                headers=None,
+                                extra_form_data=dict(
+                                    oauth2_button=self.oauth2_provider)
+                                )
+
+        respdata = 'Gravatar image for %s' % profile['email']
+        self.assertTrue(respdata in res.data.decode('utf8'))
+
+    def mock_user_profile(self):
+        profile = {'email': 'oauth2@gmail.com'}
+
+        AuthSourceRegistry.registry[OAUTH2].get_user_profile = MagicMock(
+            return_value=profile)
+        return profile
+
+    def tearDown(self):
+        self.tester.logout()
+
+    @classmethod
+    def tearDownClass(cls):
+        """
+        We need to again login the test client as soon as test scenarios
+        finishes.
+        """
+        cls.tester.logout()
+        app_config.AUTHENTICATION_SOURCES = [INTERNAL]
+        utils.login_tester_account(cls.tester)
diff --git a/web/pgadmin/messages.pot b/web/pgadmin/messages.pot
index b3975a058..307e5c235 100644
--- a/web/pgadmin/messages.pot
+++ b/web/pgadmin/messages.pot
@@ -29,7 +29,11 @@ msgstr ""
 msgid "403 FORBIDDEN"
 msgstr ""
 
-#: pgadmin/about/__init__.py:40
+#: pgadmin/__init__.py:332 pgadmin/authenticate/internal.py:712
+msgid "OAuth is disabled because master password is required."
+msgstr ""
+
+#: pgadmin/about/__init__.py:36
 #, python-format
 msgid "About %(appname)s"
 msgstr ""
@@ -209,6 +213,9 @@ msgstr ""
 
 #: pgadmin/authenticate/ldap.py:270
 msgid "Could not find the specified user."
+#: pgadmin/authenticate/oauth.py:16
+#: pgadmin/templates/security/login_user.html:29
+msgid "Log in with oauth"
 msgstr ""
 
 #: pgadmin/authenticate/registry.py:50
diff --git a/web/pgadmin/misc/bgprocess/processes.py b/web/pgadmin/misc/bgprocess/processes.py
index 25e0a2a9e..4da7896a0 100644
--- a/web/pgadmin/misc/bgprocess/processes.py
+++ b/web/pgadmin/misc/bgprocess/processes.py
@@ -280,7 +280,7 @@ class BatchProcess(object):
         env['OUTDIR'] = self.log_dir
         env['PGA_BGP_FOREGROUND'] = "1"
         if config.SERVER_MODE and session and \
-                session['_auth_source_manager_obj']['current_source'] == \
+                session['auth_source_manager']['current_source'] == \
                 KERBEROS:
             env['KRB5CCNAME'] = session['KRB5CCNAME']
 
diff --git a/web/pgadmin/static/scss/_pgadmin.style.scss b/web/pgadmin/static/scss/_pgadmin.style.scss
index 6a185471b..51c3b1dd5 100644
--- a/web/pgadmin/static/scss/_pgadmin.style.scss
+++ b/web/pgadmin/static/scss/_pgadmin.style.scss
@@ -946,6 +946,9 @@ table.table-empty-rows{
   & .btn-login {
     background-color: $security-btn-color;
   }
+  .btn-oauth {
+    background-color: $security-btn-color;
+  }
   & .user-language {
     & select{
       background-color: $color-primary;
diff --git a/web/pgadmin/templates/security/login_user.html b/web/pgadmin/templates/security/login_user.html
index 2e92d7b12..884562d21 100644
--- a/web/pgadmin/templates/security/login_user.html
+++ b/web/pgadmin/templates/security/login_user.html
@@ -12,7 +12,7 @@
     {% set user_language = request.cookies.get('PGADMIN_LANGUAGE') or 'en' %}
     {{ render_username_with_errors(login_user_form.email, "text") }}
     {{ render_field_with_errors(login_user_form.password, "password") }}
-    <button class="btn btn-primary btn-block btn-login" type="submit" value="{{ _('Login') }}">{{ _('Login') }}</button>
+    <button name="internal_button" {% if (config.OAUTH2 in config.AUTHENTICATION_SOURCES or config.KERBEROS in config.AUTHENTICATION_SOURCES) and config.AUTHENTICATION_SOURCES | length == 1 %} disabled {% endif %} class="btn btn-primary btn-block btn-login" type="submit" value="{{ _('Login') }}">{{ _('Login') }}</button>
     <div class="form-group row mb-3 c user-language">
         <div class="col-7"><span class="help-block">{{ _('<a href="%(url)s" class="text-white">Forgotten your password</a>?', url=url_for('browser.forgot_password')) }}</span></div>
         <div class="col-5">
@@ -20,9 +20,16 @@
                 {% for key, lang in config.LANGUAGES.items() %}
                 <option value="{{key}}" {% if user_language == key %}selected{% endif %}>{{lang}}</option>
                 {% endfor %}
-             </select>
+            </select>
         </div>
     </div>
+{% if config.OAUTH2 in config.AUTHENTICATION_SOURCES and config.AUTHENTICATION_SOURCES %}
+    {% for oauth_config in config.OAUTH2_CONFIG %}
+        <button name="oauth2_button" class="btn btn-primary btn-block btn-oauth" style="background-color: {{oauth_config['OAUTH2_BUTTON_COLOR']}}" value="{{ oauth_config['OAUTH2_NAME'] }}" type="submit">
+            <i class="fab {{ oauth_config['OAUTH2_ICON'] }} fa-lg mr-2" aria-hidden="true" role="image"></i>
+            {{ _('Login with %(oauth_name)s', oauth_name=oauth_config['OAUTH2_DISPLAY_NAME']) }}</button>
+    {% endfor %}
+{% endif %}
 </form>
 {% endif %}
 {% endblock %}
diff --git a/web/pgadmin/tools/user_management/__init__.py b/web/pgadmin/tools/user_management/__init__.py
index 5d56081ba..3aabd7749 100644
--- a/web/pgadmin/tools/user_management/__init__.py
+++ b/web/pgadmin/tools/user_management/__init__.py
@@ -176,7 +176,7 @@ def current_user_info():
             config.ALLOW_SAVE_TUNNEL_PASSWORD and session[
                 'allow_save_password'] else 'false',
             auth_sources=config.AUTHENTICATION_SOURCES,
-            current_auth_source=session['_auth_source_manager_obj'][
+            current_auth_source=session['auth_source_manager'][
                 'current_source'] if config.SERVER_MODE is True else INTERNAL
         ),
         status=200,
diff --git a/web/pgadmin/tools/user_management/static/js/user_management.js b/web/pgadmin/tools/user_management/static/js/user_management.js
index e436f4948..ffed1d44d 100644
--- a/web/pgadmin/tools/user_management/static/js/user_management.js
+++ b/web/pgadmin/tools/user_management/static/js/user_management.js
@@ -28,6 +28,7 @@ define([
     DEFAULT_AUTH_SOURCE = pgConst['INTERNAL'],
     LDAP = pgConst['LDAP'],
     KERBEROS = pgConst['KERBEROS'],
+    OAUTH2 = pgConst['OAUTH2'],
     AUTH_ONLY_INTERNAL = (userInfo['auth_sources'].length  == 1 && userInfo['auth_sources'].includes(DEFAULT_AUTH_SOURCE)) ? true : false,
     userFilter = function(collection) {
       return (new Backgrid.Extension.ClientSideFilter({
@@ -607,6 +608,16 @@ define([
                   this.get('username')
                 );
 
+                this.errorModel.set('username', errmsg);
+                return errmsg;
+              }
+              else if (!!this.get('username') && this.collection.nonFilter.where({
+                'username': this.get('username'), 'auth_source': OAUTH2,
+              }).length > 1) {
+                errmsg = gettext('The username %s already exists.',
+                  this.get('username')
+                );
+
                 this.errorModel.set('username', errmsg);
                 return errmsg;
               }
@@ -1053,7 +1064,7 @@ define([
                   saveUser: function(m) {
                     var d = m.toJSON(true);
 
-                    if((m.isNew() && (m.get('auth_source') == LDAP || m.get('auth_source') == KERBEROS) && (!m.get('username') || !m.get('auth_source') || !m.get('role')))
+                    if((m.isNew() && (m.get('auth_source') == LDAP || m.get('auth_source') == KERBEROS || m.get('auth_source') == OAUTH2) && (!m.get('username') || !m.get('auth_source') || !m.get('role')))
                       || (m.isNew() && m.get('auth_source') == DEFAULT_AUTH_SOURCE &&  (!m.get('email') || !m.get('role') ||
                           !m.get('newPassword') || !m.get('confirmPassword') || m.get('newPassword') != m.get('confirmPassword')))
                       || (!m.isNew() && m.get('newPassword') != m.get('confirmPassword'))) {
diff --git a/web/pgadmin/utils/constants.py b/web/pgadmin/utils/constants.py
index 0800193b4..5aed41b2c 100644
--- a/web/pgadmin/utils/constants.py
+++ b/web/pgadmin/utils/constants.py
@@ -55,10 +55,12 @@ ERROR_FETCHING_DATA = gettext('Unable to fetch data.')
 INTERNAL = 'internal'
 LDAP = 'ldap'
 KERBEROS = 'kerberos'
+OAUTH2 = "oauth2"
 
 SUPPORTED_AUTH_SOURCES = [INTERNAL,
                           LDAP,
-                          KERBEROS]
+                          KERBEROS,
+                          OAUTH2]
 
 BINARY_PATHS = {
     "as_bin_paths": [
diff --git a/web/pgadmin/utils/driver/psycopg2/connection.py b/web/pgadmin/utils/driver/psycopg2/connection.py
index 3bddb9fa5..695124273 100644
--- a/web/pgadmin/utils/driver/psycopg2/connection.py
+++ b/web/pgadmin/utils/driver/psycopg2/connection.py
@@ -319,7 +319,7 @@ class Connection(BaseConnection):
                 config.APP_NAME, conn_id)
 
             if config.SERVER_MODE and \
-                    session['_auth_source_manager_obj']['current_source'] == \
+                    session['auth_source_manager']['current_source'] == \
                     KERBEROS and 'KRB5CCNAME' in session\
                     and manager.kerberos_conn:
                 lock.acquire()
@@ -353,7 +353,7 @@ class Connection(BaseConnection):
                 self._wait(pg_conn)
 
             if config.SERVER_MODE and \
-                    session['_auth_source_manager_obj']['current_source'] == \
+                    session['auth_source_manager']['current_source'] == \
                     KERBEROS:
                 environ['KRB5CCNAME'] = ''
 
@@ -378,7 +378,7 @@ class Connection(BaseConnection):
             return False, msg
         finally:
             if config.SERVER_MODE and \
-                    session['_auth_source_manager_obj']['current_source'] == \
+                    session['auth_source_manager']['current_source'] == \
                     KERBEROS and lock.locked():
                 lock.release()
 
diff --git a/web/pgadmin/utils/master_password.py b/web/pgadmin/utils/master_password.py
index f962684ff..fcf15af53 100644
--- a/web/pgadmin/utils/master_password.py
+++ b/web/pgadmin/utils/master_password.py
@@ -31,13 +31,11 @@ def get_crypt_key():
         return True, current_user.password
     # if desktop mode and master pass enabled
     elif config.MASTER_PASSWORD_REQUIRED \
-            and not config.SERVER_MODE and enc_key is None:
+            and enc_key is None:
         return False, None
-    elif config.SERVER_MODE and \
-            session['_auth_source_manager_obj']['current_source']\
-            == KERBEROS:
-        return True, session['kerberos_key'] if 'kerberos_key' in session \
-            else None
+    elif not config.MASTER_PASSWORD_REQUIRED and config.SERVER_MODE and \
+            'pass_enc_key' in session:
+        return True, session['pass_enc_key']
     else:
         return True, enc_key
 
diff --git a/web/regression/python_test_utils/csrf_test_client.py b/web/regression/python_test_utils/csrf_test_client.py
index ca4120e18..eda68d82d 100644
--- a/web/regression/python_test_utils/csrf_test_client.py
+++ b/web/regression/python_test_utils/csrf_test_client.py
@@ -88,9 +88,7 @@ class TestClient(testing.FlaskClient):
         # and make a test request context that has those cookies in it.
         environ_overrides = {}
         self.cookie_jar.inject_wsgi(environ_overrides)
-        with self.app.test_request_context(
-            "/login", environ_overrides=environ_overrides,
-        ):
+        with self.app.test_request_context():
             # Now, we call Flask-WTF's method of generating a CSRF token...
             csrf_token = generate_csrf()
             # ...which also sets a value in `flask.session`, so we need to
@@ -102,18 +100,24 @@ class TestClient(testing.FlaskClient):
             return csrf_token
 
     def login(self, email, password, _follow_redirects=False,
-              headers=None):
+              headers=None, extra_form_data=dict()):
         if config.SERVER_MODE is True:
             res = self.get('/login', follow_redirects=True)
             csrf_token = self.fetch_csrf(res)
         else:
             csrf_token = self.generate_csrf_token()
 
+        form_data = dict(
+            email=email,
+            password=password,
+            csrf_token=csrf_token
+        )
+
+        if extra_form_data:
+            form_data.update(extra_form_data)
+
         res = self.post(
-            '/authenticate/login', data=dict(
-                email=email, password=password,
-                csrf_token=csrf_token,
-            ),
+            '/authenticate/login', data=form_data,
             follow_redirects=_follow_redirects,
             headers=headers
         )
