diff --git a/app/Auth/Access/LoginService.php b/app/Auth/Access/LoginService.php
index b36adb522..f41570417 100644
--- a/app/Auth/Access/LoginService.php
+++ b/app/Auth/Access/LoginService.php
@@ -47,7 +47,7 @@ class LoginService
 
         // Authenticate on all session guards if a likely admin
         if ($user->can('users-manage') && $user->can('user-roles-manage')) {
-            $guards = ['standard', 'ldap', 'saml2', 'openid'];
+            $guards = ['standard', 'ldap', 'saml2', 'oidc'];
             foreach ($guards as $guard) {
                 auth($guard)->login($user);
             }
diff --git a/app/Auth/Access/OpenIdConnect/InvalidKeyException.php b/app/Auth/Access/OpenIdConnect/InvalidKeyException.php
new file mode 100644
index 000000000..85746cb6a
--- /dev/null
+++ b/app/Auth/Access/OpenIdConnect/InvalidKeyException.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace BookStack\Auth\Access\OpenIdConnect;
+
+class InvalidKeyException extends \Exception
+{
+
+}
\ No newline at end of file
diff --git a/app/Auth/Access/OpenIdConnect/InvalidTokenException.php b/app/Auth/Access/OpenIdConnect/InvalidTokenException.php
new file mode 100644
index 000000000..b66d0f357
--- /dev/null
+++ b/app/Auth/Access/OpenIdConnect/InvalidTokenException.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace BookStack\Auth\Access\OpenIdConnect;
+
+use Exception;
+
+class InvalidTokenException extends Exception
+{
+
+}
\ No newline at end of file
diff --git a/app/Auth/Access/OpenIdConnect/JwtSigningKey.php b/app/Auth/Access/OpenIdConnect/JwtSigningKey.php
new file mode 100644
index 000000000..c835c04c3
--- /dev/null
+++ b/app/Auth/Access/OpenIdConnect/JwtSigningKey.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace BookStack\Auth\Access\OpenIdConnect;
+
+use phpseclib3\Crypt\Common\PublicKey;
+use phpseclib3\Crypt\PublicKeyLoader;
+use phpseclib3\Crypt\RSA;
+use phpseclib3\Math\BigInteger;
+
+class JwtSigningKey
+{
+    /**
+     * @var PublicKey
+     */
+    protected $key;
+
+    /**
+     * Can be created either from a JWK parameter array or local file path to load a certificate from.
+     * Examples:
+     * 'file:///var/www/cert.pem'
+     * ['kty' => 'RSA', 'alg' => 'RS256', 'n' => 'abc123...']
+     * @param array|string $jwkOrKeyPath
+     * @throws InvalidKeyException
+     */
+    public function __construct($jwkOrKeyPath)
+    {
+        if (is_array($jwkOrKeyPath)) {
+            $this->loadFromJwkArray($jwkOrKeyPath);
+        }
+    }
+
+    /**
+     * @throws InvalidKeyException
+     */
+    protected function loadFromJwkArray(array $jwk)
+    {
+        if ($jwk['alg'] !== 'RS256') {
+            throw new InvalidKeyException("Only RS256 keys are currently supported. Found key using {$jwk['alg']}");
+        }
+
+        if ($jwk['use'] !== 'sig') {
+            throw new InvalidKeyException("Only signature keys are currently supported. Found key for use {$jwk['sig']}");
+        }
+
+        if (empty($jwk['e'] ?? '')) {
+            throw new InvalidKeyException('An "e" parameter on the provided key is expected');
+        }
+
+        if (empty($jwk['n'] ?? '')) {
+            throw new InvalidKeyException('A "n" parameter on the provided key is expected');
+        }
+
+        $n = strtr($jwk['n'] ?? '', '-_', '+/');
+
+        try {
+            /** @var RSA $key */
+            $key = PublicKeyLoader::load([
+                'e' => new BigInteger(base64_decode($jwk['e']), 256),
+                'n' => new BigInteger(base64_decode($n), 256),
+            ])->withPadding(RSA::SIGNATURE_PKCS1);
+
+            $this->key = $key;
+        } catch (\Exception $exception) {
+            throw new InvalidKeyException("Failed to load key from JWK parameters with error: {$exception->getMessage()}");
+        }
+    }
+
+    /**
+     * Use this key to sign the given content and return the signature.
+     */
+    public function verify(string $content, string $signature): bool
+    {
+        return $this->key->verify($content, $signature);
+    }
+
+}
\ No newline at end of file
diff --git a/app/Auth/Access/OpenIdConnect/OpenIdConnectAccessToken.php b/app/Auth/Access/OpenIdConnect/OpenIdConnectAccessToken.php
new file mode 100644
index 000000000..6731ec4be
--- /dev/null
+++ b/app/Auth/Access/OpenIdConnect/OpenIdConnectAccessToken.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace BookStack\Auth\Access\OpenIdConnect;
+
+use InvalidArgumentException;
+use League\OAuth2\Client\Token\AccessToken;
+
+class OpenIdConnectAccessToken extends AccessToken
+{
+    /**
+     * Constructs an access token.
+     *
+     * @param array $options An array of options returned by the service provider
+     *     in the access token request. The `access_token` option is required.
+     * @throws InvalidArgumentException if `access_token` is not provided in `$options`.
+     */
+    public function __construct(array $options = [])
+    {
+        parent::__construct($options);
+        $this->validate($options);
+    }
+
+
+    /**
+     * Validate this access token response for OIDC.
+     * As per https://openid.net/specs/openid-connect-basic-1_0.html#TokenOK.
+     */
+    private function validate(array $options): void
+    {
+        // access_token: REQUIRED. Access Token for the UserInfo Endpoint.
+        // Performed on the extended class
+
+        // token_type: REQUIRED. OAuth 2.0 Token Type value. The value MUST be Bearer, as specified in OAuth 2.0
+        // Bearer Token Usage [RFC6750], for Clients using this subset.
+        // Note that the token_type value is case-insensitive.
+        if (strtolower(($options['token_type'] ?? '')) !== 'bearer') {
+            throw new InvalidArgumentException('The response token type MUST be "Bearer"');
+        }
+
+        // id_token: REQUIRED. ID Token.
+        if (empty($options['id_token'])) {
+            throw new InvalidArgumentException('An "id_token" property must be provided');
+        }
+    }
+
+    /**
+     * Get the id token value from this access token response.
+     */
+    public function getIdToken(): string
+    {
+        return $this->getValues()['id_token'];
+    }
+
+}
\ No newline at end of file
diff --git a/app/Auth/Access/OpenIdConnect/OpenIdConnectIdToken.php b/app/Auth/Access/OpenIdConnect/OpenIdConnectIdToken.php
new file mode 100644
index 000000000..09527c3ed
--- /dev/null
+++ b/app/Auth/Access/OpenIdConnect/OpenIdConnectIdToken.php
@@ -0,0 +1,143 @@
+<?php
+
+namespace BookStack\Auth\Access\OpenIdConnect;
+
+class OpenIdConnectIdToken
+{
+    /**
+     * @var array
+     */
+    protected $header;
+
+    /**
+     * @var array
+     */
+    protected $payload;
+
+    /**
+     * @var string
+     */
+    protected $signature;
+
+    /**
+     * @var array[]|string[]
+     */
+    protected $keys;
+
+    /**
+     * @var string
+     */
+    protected $issuer;
+
+    /**
+     * @var array
+     */
+    protected $tokenParts = [];
+
+    public function __construct(string $token, string $issuer, array $keys)
+    {
+        $this->keys = $keys;
+        $this->issuer = $issuer;
+        $this->parse($token);
+    }
+
+    /**
+     * Parse the token content into its components.
+     */
+    protected function parse(string $token): void
+    {
+        $this->tokenParts = explode('.', $token);
+        $this->header = $this->parseEncodedTokenPart($this->tokenParts[0]);
+        $this->payload = $this->parseEncodedTokenPart($this->tokenParts[1] ?? '');
+        $this->signature = $this->base64UrlDecode($this->tokenParts[2] ?? '') ?: '';
+    }
+
+    /**
+     * Parse a Base64-JSON encoded token part.
+     * Returns the data as a key-value array or empty array upon error.
+     */
+    protected function parseEncodedTokenPart(string $part): array
+    {
+        $json = $this->base64UrlDecode($part) ?: '{}';
+        $decoded = json_decode($json, true);
+        return is_array($decoded) ? $decoded : [];
+    }
+
+    /**
+     * Base64URL decode. Needs some character conversions to be compatible
+     * with PHP's default base64 handling.
+     */
+    protected function base64UrlDecode(string $encoded): string
+    {
+        return base64_decode(strtr($encoded, '-_', '+/'));
+    }
+
+    /**
+     * Validate all possible parts of the id token.
+     * @throws InvalidTokenException
+     */
+    public function validate()
+    {
+        $this->validateTokenStructure();
+        $this->validateTokenSignature();
+        $this->validateTokenClaims();
+    }
+
+    /**
+     * Validate the structure of the given token and ensure we have the required pieces.
+     * As per https://datatracker.ietf.org/doc/html/rfc7519#section-7.2
+     * @throws InvalidTokenException
+     */
+    protected function validateTokenStructure(): void
+    {
+        foreach (['header', 'payload'] as $prop) {
+            if (empty($this->$prop) || !is_array($this->$prop)) {
+                throw new InvalidTokenException("Could not parse out a valid {$prop} within the provided token");
+            }
+        }
+
+        if (empty($this->signature) || !is_string($this->signature)) {
+            throw new InvalidTokenException("Could not parse out a valid signature within the provided token");
+        }
+    }
+
+    /**
+     * Validate the signature of the given token and ensure it validates against the provided key.
+     * @throws InvalidTokenException
+     */
+    protected function validateTokenSignature(): void
+    {
+        if ($this->header['alg'] !== 'RS256') {
+            throw new InvalidTokenException("Only RS256 signature validation is supported. Token reports using {$this->header['alg']}");
+        }
+
+        $parsedKeys = array_map(function($key) {
+            try {
+                return new JwtSigningKey($key);
+            } catch (InvalidKeyException $e) {
+                return null;
+            }
+        }, $this->keys);
+
+        $parsedKeys = array_filter($parsedKeys);
+
+        $contentToSign = $this->tokenParts[0] . '.' . $this->tokenParts[1];
+        foreach ($parsedKeys as $parsedKey) {
+            if ($parsedKey->verify($contentToSign, $this->signature)) {
+                return;
+            }
+        }
+
+        throw new InvalidTokenException('Token signature could not be validated using the provided keys.');
+    }
+
+    /**
+     * Validate the claims of the token.
+     * As per https://openid.net/specs/openid-connect-basic-1_0.html#IDTokenValidation
+     */
+    protected function validateTokenClaims(): void
+    {
+        // TODO
+    }
+
+}
\ No newline at end of file
diff --git a/app/Auth/Access/OpenIdConnectOAuthProvider.php b/app/Auth/Access/OpenIdConnect/OpenIdConnectOAuthProvider.php
similarity index 84%
rename from app/Auth/Access/OpenIdConnectOAuthProvider.php
rename to app/Auth/Access/OpenIdConnect/OpenIdConnectOAuthProvider.php
index 60ae2aa09..074f463cc 100644
--- a/app/Auth/Access/OpenIdConnectOAuthProvider.php
+++ b/app/Auth/Access/OpenIdConnect/OpenIdConnectOAuthProvider.php
@@ -1,7 +1,8 @@
 <?php
 
-namespace BookStack\Auth\Access;
+namespace BookStack\Auth\Access\OpenIdConnect;
 
+use League\OAuth2\Client\Grant\AbstractGrant;
 use League\OAuth2\Client\Provider\AbstractProvider;
 use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
 use League\OAuth2\Client\Provider\GenericResourceOwner;
@@ -106,4 +107,21 @@ class OpenIdConnectOAuthProvider extends AbstractProvider
     {
         return new GenericResourceOwner($response, '');
     }
+
+    /**
+     * Creates an access token from a response.
+     *
+     * The grant that was used to fetch the response can be used to provide
+     * additional context.
+     *
+     * @param array $response
+     * @param AbstractGrant $grant
+     * @return OpenIdConnectAccessToken
+     */
+    protected function createAccessToken(array $response, AbstractGrant $grant)
+    {
+        return new OpenIdConnectAccessToken($response);
+    }
+
+
 }
\ No newline at end of file
diff --git a/app/Auth/Access/OpenIdConnectService.php b/app/Auth/Access/OpenIdConnect/OpenIdConnectService.php
similarity index 87%
rename from app/Auth/Access/OpenIdConnectService.php
rename to app/Auth/Access/OpenIdConnect/OpenIdConnectService.php
index 01050b5e5..40369dee7 100644
--- a/app/Auth/Access/OpenIdConnectService.php
+++ b/app/Auth/Access/OpenIdConnect/OpenIdConnectService.php
@@ -1,5 +1,8 @@
-<?php namespace BookStack\Auth\Access;
+<?php namespace BookStack\Auth\Access\OpenIdConnect;
 
+use BookStack\Auth\Access\LoginService;
+use BookStack\Auth\Access\OpenIdConnect\OpenIdConnectOAuthProvider;
+use BookStack\Auth\Access\RegistrationService;
 use BookStack\Auth\User;
 use BookStack\Exceptions\JsonDebugException;
 use BookStack\Exceptions\OpenIdConnectException;
@@ -8,6 +11,11 @@ use BookStack\Exceptions\UserRegistrationException;
 use Exception;
 use Lcobucci\JWT\Token;
 use League\OAuth2\Client\Token\AccessToken;
+use function auth;
+use function config;
+use function dd;
+use function trans;
+use function url;
 
 /**
  * Class OpenIdConnectService
@@ -122,17 +130,21 @@ class OpenIdConnectService
      * @throws UserRegistrationException
      * @throws StoppedAuthenticationException
      */
-    protected function processAccessTokenCallback(AccessToken $accessToken): User
+    protected function processAccessTokenCallback(OpenIdConnectAccessToken $accessToken): User
     {
-        dd($accessToken->getValues());
+        $idTokenText = $accessToken->getIdToken();
+        $idToken = new OpenIdConnectIdToken(
+            $idTokenText,
+            $this->config['issuer'],
+            [$this->config['jwt_public_key']]
+        );
+
         // TODO - Create a class to manage token parsing and validation on this
-        // Using the config params:
-        // $this->config['jwt_public_key']
-        // $this->config['issuer']
-        //
         // Ensure ID token validation is done:
         // https://openid.net/specs/openid-connect-basic-1_0.html#IDTokenValidation
         // To full affect and tested
+        // JWT signature algorthims:
+        // https://datatracker.ietf.org/doc/html/rfc7518#section-3
 
         $userDetails = $this->getUserDetails($accessToken->getIdToken());
         $isLoggedIn = auth()->check();
diff --git a/app/Http/Controllers/Auth/OpenIdConnectController.php b/app/Http/Controllers/Auth/OpenIdConnectController.php
index 23cfbbcbe..8156773b4 100644
--- a/app/Http/Controllers/Auth/OpenIdConnectController.php
+++ b/app/Http/Controllers/Auth/OpenIdConnectController.php
@@ -2,7 +2,7 @@
 
 namespace BookStack\Http\Controllers\Auth;
 
-use BookStack\Auth\Access\OpenIdConnectService;
+use BookStack\Auth\Access\OpenIdConnect\OpenIdConnectService;
 use BookStack\Http\Controllers\Controller;
 use Illuminate\Http\Request;
 
diff --git a/composer.json b/composer.json
index 288f55991..e53d9d25a 100644
--- a/composer.json
+++ b/composer.json
@@ -27,6 +27,7 @@
         "league/html-to-markdown": "^5.0.0",
         "nunomaduro/collision": "^3.1",
         "onelogin/php-saml": "^4.0",
+        "phpseclib/phpseclib": "~3.0",
         "pragmarx/google2fa": "^8.0",
         "predis/predis": "^1.1.6",
         "socialiteproviders/discord": "^4.1",
diff --git a/composer.lock b/composer.lock
index 9355deed3..62b2aa621 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "620412108a5d19ed91d9fe42418b63b5",
+    "content-hash": "5cbbf417bd19cd2164f91b9b2d38600c",
     "packages": [
         {
             "name": "aws/aws-crt-php",
@@ -3511,6 +3511,117 @@
             ],
             "time": "2021-08-28T21:27:29+00:00"
         },
+        {
+            "name": "phpseclib/phpseclib",
+            "version": "3.0.10",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpseclib/phpseclib.git",
+                "reference": "62fcc5a94ac83b1506f52d7558d828617fac9187"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/62fcc5a94ac83b1506f52d7558d828617fac9187",
+                "reference": "62fcc5a94ac83b1506f52d7558d828617fac9187",
+                "shasum": ""
+            },
+            "require": {
+                "paragonie/constant_time_encoding": "^1|^2",
+                "paragonie/random_compat": "^1.4|^2.0|^9.99.99",
+                "php": ">=5.6.1"
+            },
+            "require-dev": {
+                "phing/phing": "~2.7",
+                "phpunit/phpunit": "^5.7|^6.0|^9.4",
+                "squizlabs/php_codesniffer": "~2.0"
+            },
+            "suggest": {
+                "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
+                "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
+                "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
+                "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "phpseclib/bootstrap.php"
+                ],
+                "psr-4": {
+                    "phpseclib3\\": "phpseclib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jim Wigginton",
+                    "email": "terrafrost@php.net",
+                    "role": "Lead Developer"
+                },
+                {
+                    "name": "Patrick Monnerat",
+                    "email": "pm@datasphere.ch",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Andreas Fischer",
+                    "email": "bantu@phpbb.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Hans-Jürgen Petrich",
+                    "email": "petrich@tronic-media.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Graham Campbell",
+                    "email": "graham@alt-three.com",
+                    "role": "Developer"
+                }
+            ],
+            "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
+            "homepage": "http://phpseclib.sourceforge.net",
+            "keywords": [
+                "BigInteger",
+                "aes",
+                "asn.1",
+                "asn1",
+                "blowfish",
+                "crypto",
+                "cryptography",
+                "encryption",
+                "rsa",
+                "security",
+                "sftp",
+                "signature",
+                "signing",
+                "ssh",
+                "twofish",
+                "x.509",
+                "x509"
+            ],
+            "support": {
+                "issues": "https://github.com/phpseclib/phpseclib/issues",
+                "source": "https://github.com/phpseclib/phpseclib/tree/3.0.10"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/terrafrost",
+                    "type": "github"
+                },
+                {
+                    "url": "https://www.patreon.com/phpseclib",
+                    "type": "patreon"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2021-08-16T04:24:45+00:00"
+        },
         {
             "name": "pragmarx/google2fa",
             "version": "8.0.0",