Index: include/http_request.h
===================================================================
--- include/http_request.h	(revision 1875208)
+++ include/http_request.h	(working copy)
@@ -466,6 +466,19 @@
 AP_DECLARE_HOOK(int,auth_checker,(request_rec *r))
 
 /**
+ * This hook is used to parse any tokens in the request that might key
+ * or contain metadata such as users or IP addresses that may be
+ * relevant to the request. It runs before the access checker. This
+ * hook should be registered with ap_hook_check_autht().
+ *
+ * @param r the current request
+ * @return OK, DECLINED, or HTTP_...
+ * @ingroup hooks
+ * @see ap_hook_check_authz
+ */
+AP_DECLARE_HOOK(int,token_checker,(request_rec *r))
+
+/**
  * Register a hook function that will apply additional access control to
  * the current request.
  * @param pf An access_checker hook function
@@ -501,6 +514,24 @@
                                          const char * const *aszSucc,
                                          int nOrder, int type);
 
+/**
+ * Register a hook function that will analyze the request headers, extract
+ * any tokens, and apply and metadata contained in the tokens or keyed against
+ * the tokens to the request record.
+ * @param pf A token_checker hook function
+ * @param aszPre A NULL-terminated array of strings that name modules whose
+ *               hooks should precede this one
+ * @param aszSucc A NULL-terminated array of strings that name modules whose
+ *                hooks should succeed this one
+ * @param nOrder An integer determining order before honouring aszPre and
+ *               aszSucc (for example, HOOK_MIDDLE)
+ * @param type Internal request processing mode, either
+ *             AP_AUTH_INTERNAL_PER_URI or AP_AUTH_INTERNAL_PER_CONF
+ */
+AP_DECLARE(void) ap_hook_check_autht(ap_HOOK_check_user_id_t *pf,
+                                     const char * const *aszPre,
+                                     const char * const *aszSucc,
+                                     int nOrder, int type);
 
 /**
  * Register a hook function that will analyze the request headers,
Index: include/mod_auth.h
===================================================================
--- include/mod_auth.h	(revision 1875208)
+++ include/mod_auth.h	(working copy)
@@ -36,15 +36,20 @@
 extern "C" {
 #endif
 
+#define AUTHT_PROVIDER_GROUP "autht"
 #define AUTHN_PROVIDER_GROUP "authn"
 #define AUTHZ_PROVIDER_GROUP "authz"
+#define AUTHT_PROVIDER_VERSION "0"
 #define AUTHN_PROVIDER_VERSION "0"
 #define AUTHZ_PROVIDER_VERSION "0"
+#define AUTHT_DEFAULT_PROVIDER "jwt"
 #define AUTHN_DEFAULT_PROVIDER "file"
 
+#define AUTHT_PROVIDER_NAME_NOTE "autht_provider_name"
 #define AUTHN_PROVIDER_NAME_NOTE "authn_provider_name"
 #define AUTHZ_PROVIDER_NAME_NOTE "authz_provider_name"
 
+#define AUTHT_PREFIX "TOKEN_"
 #define AUTHN_PREFIX "AUTHENTICATE_"
 #define AUTHZ_PREFIX "AUTHORIZE_"
 
@@ -70,6 +75,15 @@
 } authn_status;
 
 typedef enum {
+    AUTHT_DENIED = AUTH_DENIED,
+    AUTHT_GRANTED = AUTH_GRANTED,
+    AUTHT_GENERAL_ERROR = AUTH_GENERAL_ERROR,
+	AUTHT_MISMATCH,
+    AUTHT_EXPIRED,
+    AUTHT_INVALID
+} autht_status;
+
+typedef enum {
     AUTHZ_DENIED,
     AUTHZ_GRANTED,
     AUTHZ_NEUTRAL,
@@ -80,6 +94,8 @@
 typedef struct {
     /* Given a username and password, expected to return AUTH_GRANTED
      * if we can validate this user/password combination.
+     *
+     * Use with AUTHN_PROVIDER_VERSION / AUTHN_PROVIDER_VERSION1 providers.
      */
     authn_status (*check_password)(request_rec *r, const char *user,
                                    const char *password);
@@ -86,9 +102,16 @@
 
     /* Given a user and realm, expected to return AUTH_USER_FOUND if we
      * can find a md5 hash of 'user:realm:password'
+     *
+     * Use with AUTHN_PROVIDER_VERSION / AUTHN_PROVIDER_VERSION1 providers.
      */
     authn_status (*get_realm_hash)(request_rec *r, const char *user,
                                    const char *realm, char **rethash);
+	/* Given a username, expected to return AUTH_GRANTED
+     * if we can validate this user exists.
+     */
+    authn_status (*check_user)(request_rec *r, const char *user);
+
 } authn_provider;
 
 /* A linked-list of authn providers. */
@@ -101,6 +124,24 @@
 };
 
 typedef struct {
+    /* Given a token of a given type, expected to return AUTH_GRANTED
+     * if the token could be successfully authenticated.
+     */
+    autht_status (*check_token)(request_rec *r, const char *type,
+    		                    const char *token);
+
+} autht_provider;
+
+/* A linked-list of authn providers. */
+typedef struct autht_provider_list autht_provider_list;
+
+struct autht_provider_list {
+    const char *provider_name;
+    const autht_provider *provider;
+    autht_provider_list *next;
+};
+
+typedef struct {
     /* Given a request_rec, expected to return AUTHZ_GRANTED
      * if we can authorize user access.
      * @param r the request record
Index: modules/aaa/config.m4
===================================================================
--- modules/aaa/config.m4	(revision 1875208)
+++ modules/aaa/config.m4	(working copy)
@@ -6,6 +6,11 @@
 
 APACHE_MODPATH_INIT(aaa)
 
+dnl Token modules: modules that parse or reference a token, that may
+dnl contain or reference further data like usernames, or IP addresses.
+dnl
+APACHE_MODULE(autht_jwt, RFC7519 JSON Web Token based authentication control, , , most)
+
 dnl Authentication modules; modules checking a username and password against a
 dnl file, database, or other similar magic.
 dnl
@@ -67,6 +72,7 @@
 dnl these are the front-end authentication modules
 
 APACHE_MODULE(auth_basic, basic authentication, , , yes)
+APACHE_MODULE(auth_bearer, bearer authentication, , , yes)
 APACHE_MODULE(auth_form, form authentication, , , most)
 APACHE_MODULE(auth_digest, RFC2617 Digest authentication, , , most, [
   APR_CHECK_APR_DEFINE(APR_HAS_RANDOM)
Index: modules/aaa/mod_auth_bearer.c
===================================================================
--- modules/aaa/mod_auth_bearer.c	(nonexistent)
+++ modules/aaa/mod_auth_bearer.c	(working copy)
@@ -0,0 +1,422 @@
+/* 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.
+ */
+
+/**
+ * This module adds support for https://tools.ietf.org/html/rfc6750 Bearer
+ * tokens, both as a generator of bearer tokens, and as an acceptor of
+ * Bearer tokens for authentication.
+ */
+
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "apr_lib.h"            /* for apr_isspace */
+#define APR_WANT_STRFUNC        /* for strcasecmp */
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "ap_provider.h"
+#include "ap_expr.h"
+
+#include "mod_auth.h"
+
+module AP_MODULE_DECLARE_DATA auth_bearer_module;
+
+typedef struct {
+    autht_provider_list *providers;
+    int authoritative;
+    ap_expr_info_t *proxy;
+    int authoritative_set:1;
+    int proxy_set:1;
+} auth_bearer_config_rec;
+
+static void *create_auth_bearer_dir_config(apr_pool_t *p, char *d)
+{
+    auth_bearer_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
+
+    /* Any failures are fatal. */
+    conf->authoritative = 1;
+
+    return conf;
+}
+
+static void *merge_auth_bearer_dir_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+    auth_bearer_config_rec *newconf = apr_pcalloc(p, sizeof(*newconf));
+    auth_bearer_config_rec *base = basev;
+    auth_bearer_config_rec *overrides = overridesv;
+
+    newconf->authoritative =
+            overrides->authoritative_set ? overrides->authoritative :
+                    base->authoritative;
+    newconf->authoritative_set = overrides->authoritative_set
+            || base->authoritative_set;
+
+    newconf->providers = overrides->providers ? overrides->providers : base->providers;
+
+    newconf->proxy =
+            overrides->proxy_set ? overrides->proxy : base->proxy;
+    newconf->proxy_set = overrides->proxy_set || base->proxy_set;
+
+    return newconf;
+}
+
+static const char *add_autht_provider(cmd_parms *cmd, void *config,
+                                      const char *arg)
+{
+    auth_bearer_config_rec *conf = (auth_bearer_config_rec*)config;
+    autht_provider_list *newp;
+
+    newp = apr_pcalloc(cmd->pool, sizeof(autht_provider_list));
+    newp->provider_name = arg;
+
+    /* lookup and cache the actual provider now */
+    newp->provider = ap_lookup_provider(AUTHT_PROVIDER_GROUP,
+                                        newp->provider_name,
+                                        AUTHT_PROVIDER_VERSION);
+
+    if (newp->provider == NULL) {
+        /* by the time they use it, the provider should be loaded and
+           registered with us. */
+        return apr_psprintf(cmd->pool,
+                            "Unknown Autht provider: %s",
+                            newp->provider_name);
+    }
+
+    if (!newp->provider->check_token) {
+        /* if it doesn't provide the appropriate function, reject it */
+        return apr_psprintf(cmd->pool,
+                            "The '%s' Autht provider doesn't support "
+                            "Bearer Authentication", newp->provider_name);
+    }
+
+    /* Add it to the list now. */
+    if (!conf->providers) {
+        conf->providers = newp;
+    }
+    else {
+        autht_provider_list *last = conf->providers;
+
+        while (last->next) {
+            last = last->next;
+        }
+        last->next = newp;
+    }
+
+    return NULL;
+}
+
+static const char *set_authoritative(cmd_parms * cmd, void *config, int flag)
+{
+    auth_bearer_config_rec *conf = (auth_bearer_config_rec *) config;
+
+    conf->authoritative = flag;
+    conf->authoritative_set = 1;
+
+    return NULL;
+}
+
+static const char *set_bearer_proxy(cmd_parms * cmd, void *config,
+        const char *user)
+{
+    auth_bearer_config_rec *conf = (auth_bearer_config_rec *) config;
+    const char *err;
+
+    if (!strcasecmp(user, "off")) {
+        conf->proxy = NULL;
+        conf->proxy_set = 1;
+    }
+    else {
+
+        conf->proxy =
+                ap_expr_parse_cmd(cmd, user, AP_EXPR_FLAG_STRING_RESULT,
+                        &err, NULL);
+        if (err) {
+            return apr_psprintf(cmd->pool,
+                    "Could not parse proxy expression '%s': %s", user,
+                    err);
+        }
+        conf->proxy_set = 1;
+    }
+
+    return NULL;
+}
+
+static const command_rec auth_bearer_cmds[] =
+{
+    AP_INIT_ITERATE("AuthBearerProvider", add_autht_provider, NULL, OR_AUTHCFG,
+                    "specify the auth providers for a directory or location"),
+    AP_INIT_FLAG("AuthBearerAuthoritative", set_authoritative, NULL, OR_AUTHCFG,
+                 "Set to 'Off' to allow access control to be passed along to "
+                 "lower modules if the token is not known to this module"),
+    AP_INIT_TAKE1("AuthBearerProxy", set_bearer_proxy, NULL, OR_AUTHCFG,
+                  "Pass a bearer authentication token over a proxt connection "
+                  "generated using the given expression, 'off' to disable."),
+    {NULL}
+};
+
+/* These functions return 0 if client is OK, and proper error status
+ * if not... either HTTP_UNAUTHORIZED, if we made a check, and it failed, or
+ * HTTP_INTERNAL_SERVER_ERROR, if things are so totally confused that we
+ * couldn't figure out how to tell if the client is authorized or not.
+ *
+ * If they return DECLINED, and all other modules also decline, that's
+ * treated by the server core as a configuration error, logged and
+ * reported as such.
+ */
+
+static void note_bearer_auth_failure(request_rec *r)
+{
+    apr_table_setn(r->err_headers_out,
+                   (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authenticate"
+                                                   : "WWW-Authenticate",
+                   apr_pstrcat(r->pool, "Bearer realm=\"", ap_auth_name(r),
+                               "\"", NULL));
+}
+
+static int hook_note_bearer_auth_failure(request_rec *r, const char *auth_type)
+{
+    if (strcasecmp(auth_type, "Bearer"))
+        return DECLINED;
+
+    note_bearer_auth_failure(r);
+    return OK;
+}
+
+static int get_bearer_auth(request_rec *r, const char **token)
+{
+    const char *auth_line;
+
+    /* Get the appropriate header */
+    auth_line = apr_table_get(r->headers_in, (PROXYREQ_PROXY == r->proxyreq)
+                                              ? "Proxy-Authorization"
+                                              : "Authorization");
+
+    if (!auth_line) {
+        note_bearer_auth_failure(r);
+        return HTTP_UNAUTHORIZED;
+    }
+
+    if (strcasecmp(ap_getword(r->pool, &auth_line, ' '), "Bearer")) {
+        /* Client tried to authenticate using wrong auth scheme */
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01614)
+                      "client used wrong authentication scheme: %s", r->uri);
+        note_bearer_auth_failure(r);
+        return HTTP_UNAUTHORIZED;
+    }
+
+    /* Skip leading spaces. */
+    while (apr_isspace(*auth_line)) {
+        auth_line++;
+    }
+
+    *token = auth_line;
+
+    return OK;
+}
+
+/* Determine the token, and check if we can process the token, for HTTP
+ * bearer authentication...
+ */
+static int authenticate_bearer_token(request_rec *r)
+{
+    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
+                                                       &auth_bearer_module);
+    const char *sent_token, *current_auth;
+    int res;
+    autht_status auth_result;
+    autht_provider_list *current_provider;
+
+    /* Are we configured to be Bearer auth? */
+    current_auth = ap_auth_type(r);
+    if (!current_auth || strcasecmp(current_auth, "Bearer")) {
+        return DECLINED;
+    }
+
+    /* We need an authentication realm. */
+    if (!ap_auth_name(r)) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01615)
+                      "need AuthName: %s", r->uri);
+        return HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    r->ap_auth_type = (char*)current_auth;
+
+    res = get_bearer_auth(r, &sent_token);
+    if (res) {
+        return res;
+    }
+
+    current_provider = conf->providers;
+    do {
+        const autht_provider *provider;
+
+        /* For now, if a provider isn't set, we'll be nice and use the jwt
+         * provider.
+         */
+        if (!current_provider) {
+            provider = ap_lookup_provider(AUTHT_PROVIDER_GROUP,
+                                          AUTHT_DEFAULT_PROVIDER,
+                                          AUTHT_PROVIDER_VERSION);
+
+            if (!provider || !provider->check_token) {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01616)
+                              "No Autht provider configured");
+                auth_result = AUTHT_GENERAL_ERROR;
+                break;
+            }
+            apr_table_setn(r->notes, AUTHT_PROVIDER_NAME_NOTE, AUTHT_DEFAULT_PROVIDER);
+        }
+        else {
+            provider = current_provider->provider;
+            apr_table_setn(r->notes, AUTHT_PROVIDER_NAME_NOTE, current_provider->provider_name);
+        }
+
+        auth_result = provider->check_token(r, "bearer", sent_token);
+
+        apr_table_unset(r->notes, AUTHT_PROVIDER_NAME_NOTE);
+
+        /* Something occurred. Stop checking. */
+        if (auth_result != AUTH_USER_NOT_FOUND) {
+            break;
+        }
+
+        /* If we're not really configured for providers, stop now. */
+        if (!conf->providers) {
+            break;
+        }
+
+        current_provider = current_provider->next;
+    } while (current_provider);
+
+    if (auth_result != AUTHT_GRANTED) {
+        int return_code;
+
+        /* If we're not authoritative, then any error is ignored. */
+        if (!(conf->authoritative) && auth_result != AUTHT_DENIED) {
+            return DECLINED;
+        }
+
+        switch (auth_result) {
+        case AUTHT_DENIED:
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+                      "bearer token %s: authentication failure for \"%s\": "
+                      "Token Rejected",
+                      sent_token, r->uri);
+            return_code = HTTP_UNAUTHORIZED;
+            break;
+        case AUTHT_EXPIRED:
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+                      "bearer token %s: authentication failure for \"%s\": "
+                      "Token has expired",
+                      sent_token, r->uri);
+            return_code = HTTP_UNAUTHORIZED;
+            break;
+        case AUTHT_INVALID:
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+                      "bearer token %s: authentication failure for \"%s\": "
+                      "Token is not yet valid",
+                      sent_token, r->uri);
+            return_code = HTTP_UNAUTHORIZED;
+            break;
+        case AUTHT_MISMATCH:
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+                      "bearer token %s: did not match '%s': %s", sent_token,
+					  ap_auth_name(r), r->uri);
+            return_code = HTTP_UNAUTHORIZED;
+            break;
+        case AUTH_GENERAL_ERROR:
+        default:
+            /* We'll assume that the module has already said what its error
+             * was in the logs.
+             */
+            return_code = HTTP_INTERNAL_SERVER_ERROR;
+            break;
+        }
+
+        /* If we're returning 401, tell them to try again. */
+        if (return_code == HTTP_UNAUTHORIZED) {
+            note_bearer_auth_failure(r);
+        }
+        return return_code;
+    }
+
+    return OK;
+}
+
+/* If we have set claims to be made, create a bearer authentication header
+ * for the benefit of a proxy or application running behind this server.
+ */
+static int authenticate_bearer_fixup(request_rec *r)
+{
+    const char *auth_line, *token, *err;
+    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
+                                                       &auth_bearer_module);
+
+    if (!conf->proxy) {
+        return DECLINED;
+    }
+
+    token = ap_expr_str_exec(r, conf->proxy, &err);
+    if (err) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02455)
+                      "AuthBearerProxy: could not evaluate token expression for URI '%s': %s", r->uri, err);
+        return HTTP_INTERNAL_SERVER_ERROR;
+    }
+    if (!token || !*token) {
+        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02458)
+                      "AuthBearerProxy: empty token expression for URI '%s', ignoring", r->uri);
+
+        apr_table_unset(r->headers_in, "Authorization");
+
+        return DECLINED;
+    }
+
+    auth_line = apr_pstrcat(r->pool, "Bearer ", token,
+                            NULL);
+    apr_table_setn(r->headers_in, "Authorization", auth_line);
+
+    ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02457)
+                  "AuthBearerProxy: \"Authorization: %s\"",
+                  auth_line);
+
+    return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+    ap_hook_check_autht(authenticate_bearer_token, NULL, NULL, APR_HOOK_MIDDLE,
+                        AP_AUTH_INTERNAL_PER_CONF);
+    ap_hook_fixups(authenticate_bearer_fixup, NULL, NULL, APR_HOOK_LAST);
+    ap_hook_note_auth_failure(hook_note_bearer_auth_failure, NULL, NULL,
+                              APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(auth_bearer) =
+{
+    STANDARD20_MODULE_STUFF,
+    create_auth_bearer_dir_config,  /* dir config creater */
+    merge_auth_bearer_dir_config,   /* dir merger --- default is to override */
+    NULL,                           /* server config */
+    NULL,                           /* merge server config */
+    auth_bearer_cmds,               /* command apr_table_t */
+    register_hooks                  /* register hooks */
+};
Index: modules/aaa/mod_autht_jwt.c
===================================================================
--- modules/aaa/mod_autht_jwt.c	(nonexistent)
+++ modules/aaa/mod_autht_jwt.c	(working copy)
@@ -0,0 +1,1164 @@
+/* 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.
+ */
+
+/**
+ * This module adds support for https://tools.ietf.org/html/rfc6750 Bearer
+ * tokens, both as a generator of bearer tokens, and as an acceptor of
+ * Bearer tokens for authentication.
+ */
+
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "apr_crypto.h"
+#include "apr_jose.h"
+#include "apr_lib.h"            /* for apr_isspace */
+#include "apr_base64.h"         /* for apr_base64_decode et al */
+#define APR_WANT_STRFUNC        /* for strcasecmp */
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "util_md5.h"
+#include "ap_provider.h"
+#include "ap_expr.h"
+
+#include "mod_auth.h"
+
+#define CRYPTO_KEY "auth_bearer_context"
+
+module AP_MODULE_DECLARE_DATA autht_jwt_module;
+
+typedef enum jws_alg_type_e {
+    /** No specific type. */
+    JWS_ALG_TYPE_NONE = 0,
+    /** HMAC SHA256 */
+    JWS_ALG_TYPE_HS256 = 1,
+} jws_alg_type_e;
+
+typedef struct {
+    unsigned char *secret;
+    apr_size_t secret_len;
+    jws_alg_type_e jws_alg;
+} auth_bearer_signature_rec;
+
+typedef struct {
+    authn_provider_list *providers;
+    int authoritative;
+    apr_hash_t *claims;
+    apr_array_header_t *signs;
+    apr_array_header_t *verifies;
+    int signs_set:1;
+    int verifies_set:1;
+    int fake_set:1;
+    int authoritative_set:1;
+} auth_bearer_config_rec;
+
+typedef struct {
+    const char *library;
+    const char *params;
+    apr_crypto_t **crypto;
+    int library_set;
+} auth_bearer_conf;
+
+static int auth_bearer_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
+        server_rec *s) {
+    const apr_crypto_driver_t *driver = NULL;
+
+    /* auth_bearer_init() will be called twice. Don't bother
+     * going through all of the initialization on the first call
+     * because it will just be thrown away.*/
+    if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
+        return OK;
+    }
+
+    while (s) {
+
+        auth_bearer_conf *conf = ap_get_module_config(s->module_config,
+                &autht_jwt_module);
+
+        if (conf->library_set && !*conf->crypto) {
+
+            const apu_err_t *err = NULL;
+            apr_status_t rv;
+
+            rv = apr_crypto_init(p);
+            if (APR_SUCCESS != rv) {
+                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                        APLOGNO() "APR crypto could not be initialised");
+                return rv;
+            }
+
+            rv = apr_crypto_get_driver(&driver, conf->library, conf->params,
+                    &err, p);
+            if (APR_EREINIT == rv) {
+                ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s,
+                        APLOGNO() "warning: crypto for '%s' was already initialised, " "using existing configuration",
+                        conf->library);
+                rv = APR_SUCCESS;
+            }
+            if (APR_SUCCESS != rv && err) {
+                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                        APLOGNO() "The crypto library '%s' could not be loaded: %s (%s: %d)",
+                        conf->library, err->msg, err->reason, err->rc);
+                return rv;
+            }
+            if (APR_ENOTIMPL == rv) {
+                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                        APLOGNO() "The crypto library '%s' could not be found",
+                        conf->library);
+                return rv;
+            }
+            if (APR_SUCCESS != rv || !driver) {
+                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                        APLOGNO() "The crypto library '%s' could not be loaded",
+                        conf->library);
+                return rv;
+            }
+
+            rv = apr_crypto_make(conf->crypto, driver, conf->params, p);
+            if (APR_SUCCESS != rv) {
+                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                        APLOGNO() "The crypto library '%s' could not be initialised",
+                        conf->library);
+                return rv;
+            }
+
+            ap_log_error(APLOG_MARK, APLOG_INFO, rv, s,
+                    APLOGNO() "The crypto library '%s' was loaded successfully",
+                    conf->library);
+
+        }
+
+        s = s->next;
+    }
+
+    return OK;
+}
+
+static void *create_auth_bearer_config(apr_pool_t * p, server_rec *s)
+{
+    auth_bearer_conf *new =
+    (auth_bearer_conf *) apr_pcalloc(p, sizeof(auth_bearer_conf));
+
+    /* if no library has been configured, set the recommended library
+     * as a sensible default.
+     */
+#ifdef APU_CRYPTO_RECOMMENDED_DRIVER
+    new->library = APU_CRYPTO_RECOMMENDED_DRIVER;
+#endif
+    new->crypto = apr_pcalloc(p, sizeof(apr_crypto_t *));
+
+    return (void *) new;
+}
+
+static void *merge_auth_bearer_config(apr_pool_t * p, void *basev, void *addv)
+{
+    auth_bearer_conf *new = (auth_bearer_conf *) apr_pcalloc(p, sizeof(auth_bearer_conf));
+    auth_bearer_conf *add = (auth_bearer_conf *) addv;
+    auth_bearer_conf *base = (auth_bearer_conf *) basev;
+
+    new->library = (add->library_set == 0) ? base->library : add->library;
+    new->params = (add->library_set == 0) ? base->params : add->params;
+    new->library_set = add->library_set || base->library_set;
+
+    new->crypto = base->crypto;
+
+    return (void *) new;
+}
+
+static void *create_auth_bearer_dir_config(apr_pool_t *p, char *d)
+{
+    auth_bearer_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
+
+    conf->claims = apr_hash_make(p);
+    conf->signs = apr_array_make(p, 1, sizeof(auth_bearer_signature_rec));
+    conf->verifies = apr_array_make(p, 1, sizeof(auth_bearer_signature_rec));
+
+    /* Any failures are fatal. */
+    conf->authoritative = 1;
+
+    return conf;
+}
+
+static void *merge_auth_bearer_dir_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+    auth_bearer_config_rec *newconf = apr_pcalloc(p, sizeof(*newconf));
+    auth_bearer_config_rec *base = basev;
+    auth_bearer_config_rec *overrides = overridesv;
+
+    newconf->claims = apr_hash_overlay(p, overrides->claims,
+                                          base->claims);
+
+    newconf->signs =
+            overrides->signs_set ? overrides->signs : base->signs;
+    newconf->signs_set = overrides->signs_set || base->signs_set;
+
+    newconf->verifies =
+            overrides->verifies_set ? overrides->verifies : base->verifies;
+    newconf->verifies_set = overrides->verifies_set || base->verifies_set;
+
+    newconf->authoritative =
+            overrides->authoritative_set ? overrides->authoritative :
+                    base->authoritative;
+    newconf->authoritative_set = overrides->authoritative_set
+            || base->authoritative_set;
+
+    newconf->providers = overrides->providers ? overrides->providers : base->providers;
+
+    return newconf;
+}
+
+static const char *add_autht_provider(cmd_parms *cmd, void *config,
+                                      const char *arg)
+{
+    auth_bearer_config_rec *conf = (auth_bearer_config_rec*)config;
+    authn_provider_list *newp;
+
+    newp = apr_pcalloc(cmd->pool, sizeof(authn_provider_list));
+    newp->provider_name = arg;
+
+    /* lookup and cache the actual provider now */
+    newp->provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
+                                        newp->provider_name,
+                                        AUTHN_PROVIDER_VERSION);
+
+    if (newp->provider == NULL) {
+        /* by the time they use it, the provider should be loaded and
+           registered with us. */
+        return apr_psprintf(cmd->pool,
+                            "Unknown Authn provider: %s",
+                            newp->provider_name);
+    }
+
+    if (!newp->provider->check_password) {
+        /* if it doesn't provide the appropriate function, reject it */
+        return apr_psprintf(cmd->pool,
+                            "The '%s' Authn provider doesn't support "
+                            "Basic Authentication", newp->provider_name);
+    }
+
+    /* Add it to the list now. */
+    if (!conf->providers) {
+        conf->providers = newp;
+    }
+    else {
+        authn_provider_list *last = conf->providers;
+
+        while (last->next) {
+            last = last->next;
+        }
+        last->next = newp;
+    }
+
+    return NULL;
+}
+
+static const char *set_authoritative(cmd_parms * cmd, void *config, int flag)
+{
+    auth_bearer_config_rec *conf = (auth_bearer_config_rec *) config;
+
+    conf->authoritative = flag;
+    conf->authoritative_set = 1;
+
+    return NULL;
+}
+
+static const char *set_jwt_claim(cmd_parms *cmd, void *config,
+        const char *op, const char *key, const char *expression)
+{
+    auth_bearer_config_rec *conf = (auth_bearer_config_rec *) config;
+    const char *err;
+
+    if (!strcasecmp(op, "set")) {
+        ap_expr_info_t *expr;
+
+        expr = ap_expr_parse_cmd(cmd, expression, AP_EXPR_FLAG_STRING_RESULT,
+                &err, NULL);
+        if (err) {
+            return apr_psprintf(cmd->pool,
+                    "Could not parse claim '%s' expression '%s': %s", key,
+                    expression, err);
+        }
+
+        apr_hash_set(conf->claims, key, APR_HASH_KEY_STRING, expr);
+
+    } else if (!strcasecmp(op, "unset")) {
+
+        apr_hash_set(conf->claims, key, APR_HASH_KEY_STRING, NULL);
+
+    } else {
+
+        return apr_psprintf(cmd->pool,
+                "Could not parse claim operation '%s', "
+                "values should be 'set' or 'unset'", op);
+
+    }
+
+    return NULL;
+}
+
+static const char *set_jwt_sign(cmd_parms * cmd, void *config,
+        const char *alg, const char *type, const char *sig)
+{
+    auth_bearer_config_rec *dconf = (auth_bearer_config_rec *) config;
+
+    auth_bearer_signature_rec *srec = apr_array_push(dconf->signs);
+
+    /* handle the algorithm */
+    if (!strcasecmp(alg, "none")) {
+        srec->jws_alg = JWS_ALG_TYPE_NONE;
+        if (type || sig) {
+            return "AuthtJwtSign: algorithm 'none' has extra parameters";
+        }
+    }
+    else if (!strcasecmp(alg, "HS256")) {
+        srec->jws_alg = JWS_ALG_TYPE_HS256;
+    }
+    else {
+        return apr_psprintf(cmd->pool, "AuthtJwtSign: algorithm not supported: %s", alg);
+    }
+
+    /* handle the file */
+    if (type) {
+        if (!strcasecmp(type, "file")) {
+            apr_file_t *file;
+            apr_finfo_t finfo;
+            apr_status_t status;
+
+            sig = ap_server_root_relative(cmd->temp_pool, sig);
+
+            status = apr_file_open(&file, sig, APR_READ | APR_BUFFERED,
+            APR_OS_DEFAULT, cmd->pool);
+            if (status != APR_SUCCESS) {
+                char buf[1024];
+                apr_strerror(status, buf, sizeof(buf));
+                return apr_psprintf(cmd->pool,
+                        "AuthtJwtSign: file '%s' could not be opened: %s", sig,
+                        buf);
+            }
+
+            status = apr_file_info_get(&finfo, APR_FINFO_TYPE | APR_FINFO_SIZE,
+                    file);
+            if (status != APR_SUCCESS) {
+                char buf[1024];
+                apr_strerror(status, buf, sizeof(buf));
+                return apr_psprintf(cmd->pool,
+                        "AuthtJwtSign: info could not be obtained for '%s': %s",
+                        sig, buf);
+            }
+
+            srec->secret = apr_palloc(cmd->pool, finfo.size);
+            srec->secret_len = finfo.size;
+
+            status = apr_file_read_full(file, srec->secret,
+                    srec->secret_len, NULL);
+            if (status != APR_SUCCESS) {
+                char buf[1024];
+                apr_strerror(status, buf, sizeof(buf));
+                return apr_psprintf(cmd->pool,
+                        "AuthtJwtSign: file '%s' could not be read: %s", sig,
+                        buf);
+            }
+
+            apr_file_close(file);
+
+        }
+        else {
+            return apr_psprintf(cmd->pool,
+                    "AuthtJwtSign: parameter '%s' is not 'file'", type);
+        }
+    }
+
+    dconf->signs_set = 1;
+
+    return NULL;
+}
+
+static const char *set_jwt_verify(cmd_parms * cmd, void *config,
+        const char *alg, const char *type, const char *sig)
+{
+    auth_bearer_config_rec *dconf = (auth_bearer_config_rec *) config;
+
+    auth_bearer_signature_rec *srec = apr_array_push(dconf->verifies);
+
+    /* handle the algorithm */
+    if (!strcasecmp(alg, "none")) {
+        srec->jws_alg = JWS_ALG_TYPE_NONE;
+        if (type || sig) {
+            return "AuthtJwtVerify: algorithm 'none' has extra parameters";
+        }
+    }
+    else if (!strcasecmp(alg, "HS256")) {
+        srec->jws_alg = JWS_ALG_TYPE_HS256;
+    }
+    else {
+        return apr_psprintf(cmd->pool, "AuthtJwtVerify: algorithm not supported: %s", alg);
+    }
+
+    /* handle the file */
+    if (type) {
+        if (!strcasecmp(type, "file")) {
+            apr_file_t *file;
+            apr_finfo_t finfo;
+            apr_status_t status;
+
+            sig = ap_server_root_relative(cmd->temp_pool, sig);
+
+            status = apr_file_open(&file, sig, APR_READ | APR_BUFFERED,
+            APR_OS_DEFAULT, cmd->pool);
+            if (status != APR_SUCCESS) {
+                char buf[1024];
+                apr_strerror(status, buf, sizeof(buf));
+                return apr_psprintf(cmd->pool,
+                        "AuthtJwtVerify: file '%s' could not be opened: %s", sig,
+                        buf);
+            }
+
+            status = apr_file_info_get(&finfo, APR_FINFO_TYPE | APR_FINFO_SIZE,
+                    file);
+            if (status != APR_SUCCESS) {
+                char buf[1024];
+                apr_strerror(status, buf, sizeof(buf));
+                return apr_psprintf(cmd->pool,
+                        "AuthtJwtVerify: info could not be obtained for '%s': %s",
+                        sig, buf);
+            }
+
+            srec->secret = apr_palloc(cmd->pool, finfo.size);
+            srec->secret_len = finfo.size;
+
+            status = apr_file_read_full(file, srec->secret,
+                    srec->secret_len, NULL);
+            if (status != APR_SUCCESS) {
+                char buf[1024];
+                apr_strerror(status, buf, sizeof(buf));
+                return apr_psprintf(cmd->pool,
+                        "AuthtJwtVerify: file '%s' could not be read: %s", sig,
+                        buf);
+            }
+
+            apr_file_close(file);
+
+        }
+        else {
+            return apr_psprintf(cmd->pool,
+                    "AuthtJwtVerify: parameter '%s' is not 'file'", type);
+        }
+    }
+
+    dconf->verifies_set = 1;
+
+    return NULL;
+}
+
+static const char *set_jwt_driver(cmd_parms * cmd, void *config, const char *arg)
+{
+    auth_bearer_conf *conf =
+            (auth_bearer_conf *)ap_get_module_config(cmd->server->module_config,
+            &autht_jwt_module);
+
+    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+    if (err != NULL) {
+        return err;
+    }
+
+    conf->library = ap_getword_conf(cmd->pool, &arg);
+    conf->params = arg;
+    conf->library_set = 1;
+
+    return NULL;
+}
+
+static const command_rec auth_bearer_cmds[] =
+{
+    AP_INIT_ITERATE("AuthtJWTProvider", add_autht_provider, NULL, OR_AUTHCFG,
+                    "specify the auth providers for a directory or location"),
+    AP_INIT_FLAG("AuthtJWTAuthoritative", set_authoritative, NULL, OR_AUTHCFG,
+                 "Set to 'Off' to allow access control to be passed along to "
+                 "lower modules if the token is not known to this module"),
+
+    AP_INIT_TAKE13("AuthtJwtVerify", set_jwt_verify, NULL, RSRC_CONF|OR_AUTHCFG,
+                   "The JWS signing algorithm and passphrase/key to verify an incoming JWT token"),
+    AP_INIT_TAKE13("AuthtJwtSign", set_jwt_sign, NULL, RSRC_CONF|OR_AUTHCFG,
+            "The JWS signing algorithm and passphrase/key to sign an outgoing JWT token"),
+
+    AP_INIT_TAKE23("AuthtJwtClaim", set_jwt_claim, NULL, OR_AUTHCFG,
+            "Set a claim with the given name and expression, or "
+            "unset the claim with the given name."),
+
+    AP_INIT_RAW_ARGS("AuthtJwtDriver", set_jwt_driver, NULL, RSRC_CONF,
+            "The underlying crypto library driver to use"),
+
+    {NULL}
+};
+
+typedef struct claim_iter_t {
+    request_rec *r;
+    apr_json_value_t *object;
+} claim_iter_t;
+
+static int claim_iter(void *ctx, const void *key, apr_ssize_t klen,
+                     const void *val)
+{
+    const char *err, *value;
+    claim_iter_t *iter = ctx;
+    request_rec *r = iter->r;
+
+    value = ap_expr_str_exec(r, val, &err);
+    if (err) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+                "AuthBearerClaim: could not evaluate '%s' expression "
+                "'%s' for URI '%s': %s",
+                (char * )key, (char * )val, r->uri, err);
+        return FALSE;
+    }
+
+
+    apr_json_object_set(iter->object, key, klen,
+            apr_json_string_create(r->pool, value, strlen(value)), r->pool);
+
+    return TRUE;
+}
+
+static apr_status_t sign_cb(apr_bucket_brigade *bb, apr_jose_t *jose,
+        apr_jose_signature_t *signature, void *ctx, apr_pool_t *pool) {
+    auth_bearer_signature_rec *srec = NULL;
+    request_rec *r = ctx;
+
+    auth_bearer_conf *sconf = ap_get_module_config(r->server->module_config,
+            &autht_jwt_module);
+
+    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
+            &autht_jwt_module);
+
+    if (conf->signs_set) {
+        srec = (auth_bearer_signature_rec *) conf->signs->elts;
+    }
+
+    if (srec) {
+        switch (srec->jws_alg) {
+        case JWS_ALG_TYPE_NONE: {
+
+            return APR_SUCCESS;
+        }
+        case JWS_ALG_TYPE_HS256: {
+            apr_bucket *e;
+            apr_crypto_key_rec_t *krec;
+            apr_crypto_key_t *key = NULL;
+            apr_crypto_digest_t *digest = NULL;
+            apr_crypto_digest_rec_t *rec;
+            char * buf;
+            apr_status_t status;
+
+            if (!*sconf->crypto) {
+                jose->result.msg = "token could not be signed";
+                jose->result.reason = "no crypto driver configured (set AuthtJwtDriver)";
+                return APR_EGENERAL;
+            }
+
+            krec = apr_crypto_key_rec_make(APR_CRYPTO_KTYPE_HMAC, pool);
+
+            krec->k.hmac.digest = APR_CRYPTO_DIGEST_SHA256;
+            krec->k.hmac.secret = srec->secret;
+            krec->k.hmac.secretLen = srec->secret_len;
+
+            status = apr_crypto_key(&key, krec, *sconf->crypto, pool);
+            if (status != APR_SUCCESS) {
+                jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                apr_strerror(status, buf, HUGE_STRING_LEN);
+                jose->result.msg = "token could not be signed";
+                return status;
+            }
+
+            rec = apr_crypto_digest_rec_make(APR_CRYPTO_DTYPE_SIGN, pool);
+
+            status = apr_crypto_digest_init(&digest, key, rec, pool);
+            if (status != APR_SUCCESS) {
+                jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                apr_strerror(status, buf, HUGE_STRING_LEN);
+                jose->result.msg = "token could not be signed";
+                return status;
+            }
+
+            for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e =
+                    APR_BUCKET_NEXT(e)) {
+                const char *str;
+                apr_size_t len;
+
+                /* If we see an EOS, don't bother doing anything more. */
+                if (APR_BUCKET_IS_EOS(e)) {
+                    break;
+                }
+
+                status = apr_bucket_read(e, &str, &len, APR_BLOCK_READ);
+                if (status != APR_SUCCESS) {
+                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                    apr_strerror(status, buf, HUGE_STRING_LEN);
+                    jose->result.msg = "token could not be signed";
+                    return status;
+                }
+
+                status = apr_crypto_digest_update(digest,
+                        (const unsigned char *) str, len);
+                if (status != APR_SUCCESS) {
+                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                    apr_strerror(status, buf, HUGE_STRING_LEN);
+                    jose->result.msg = "token could not be signed";
+                    return status;
+                }
+            }
+
+            status = apr_crypto_digest_final(digest);
+            if (status != APR_SUCCESS) {
+                jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                apr_strerror(status, buf, HUGE_STRING_LEN);
+                jose->result.msg = "token could not be signed";
+                return status;
+            }
+
+            signature->sig.data = rec->d.sign.s;
+            signature->sig.len = rec->d.sign.slen;
+
+            return APR_SUCCESS;
+        }
+        }
+    }
+    else {
+        /* algorithm is none */
+        return APR_SUCCESS;
+    }
+
+    return APR_ENOTIMPL;
+}
+
+/* If we have set claims to be made, create a JWT token.
+ */
+static const char *jwt_get_token(request_rec *r)
+{
+    claim_iter_t iter = { 0 };
+    apr_json_value_t *claims;
+    apr_json_value_t *protect;
+    apr_jose_t jwt = { 0 };
+    apr_jose_t jws = { 0 };
+    apr_jose_signature_t signature = { 0 };
+    auth_bearer_signature_rec *srec = NULL;
+    apr_bucket_brigade *bb;
+    char *auth_line;
+    apr_size_t len;
+    apr_off_t offset;
+    apr_status_t status;
+
+    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
+                                                       &autht_jwt_module);
+
+    apr_jose_cb_t cb = { 0 };
+
+    cb.sign = sign_cb;
+    cb.ctx = r;
+
+    if (!conf->claims || !apr_hash_count(conf->claims)) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r,
+                APLOGNO() "AuthBearerClaim: could not encode a JWT token for URI '%s': no claims",
+                r->uri);
+        return "error:no-claims";
+    }
+
+    if (conf->verifies_set) {
+        srec = (auth_bearer_signature_rec *)conf->verifies->elts;
+    }
+
+    /* create a JWT containing the claims */
+    claims = apr_json_object_create(r->pool);
+    iter.object = claims;
+    iter.r = r;
+
+    /* iterate through our claims */
+    if (!apr_hash_do(claim_iter, &iter, conf->claims)) {
+        return "error:claim-failed";
+    }
+
+    apr_jose_jwt_make(&jwt, claims, r->pool);
+    protect = apr_json_object_create(r->pool);
+
+    apr_json_object_set(protect, APR_JOSE_JWSE_TYPE,
+            APR_JSON_VALUE_STRING,
+            apr_json_string_create(r->pool, APR_JOSE_JWSE_TYPE_JWT,
+                    APR_JSON_VALUE_STRING), r->pool);
+
+    if (srec) {
+        /* which signature type do we have? */
+        switch (srec->jws_alg) {
+        case JWS_ALG_TYPE_NONE: {
+            apr_json_object_set(protect, APR_JOSE_JWKSE_ALGORITHM,
+                    APR_JSON_VALUE_STRING,
+                    apr_json_string_create(r->pool, APR_JOSE_JWA_NONE,
+                            APR_JSON_VALUE_STRING), r->pool);
+
+            break;
+        }
+        case JWS_ALG_TYPE_HS256: {
+            apr_json_object_set(protect, APR_JOSE_JWKSE_ALGORITHM,
+                    APR_JSON_VALUE_STRING,
+                    apr_json_string_create(r->pool, APR_JOSE_JWA_HS256,
+                            APR_JSON_VALUE_STRING), r->pool);
+
+            break;
+        }
+        }
+
+    }
+    else {
+        /* no srec defaults to none */
+        apr_json_object_set(protect, APR_JOSE_JWKSE_ALGORITHM,
+                APR_JSON_VALUE_STRING,
+                apr_json_string_create(r->pool, APR_JOSE_JWA_NONE,
+                        APR_JSON_VALUE_STRING), r->pool);
+    }
+
+    apr_jose_signature_make(&signature, NULL, protect, r->pool);
+    apr_jose_jws_make(&jws, &signature, NULL, &jwt, r->pool);
+
+    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+    status = apr_jose_encode(bb, NULL, NULL, &jws, &cb, r->pool);
+    if (APR_SUCCESS != status) {
+        const apu_err_t *err = apr_jose_error(&jws);
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
+                APLOGNO() "AuthBearerClaim: could not encode a JWT token for URI '%s': %s: %s",
+                r->uri, err->msg, err->reason);
+        return "error:could-not-encode";
+    }
+
+    apr_brigade_length(bb, 1, &offset);
+    auth_line = apr_pcalloc(r->pool, offset + 1);
+    len = offset;
+    apr_brigade_flatten(bb, auth_line, &len);
+    auth_line[offset] = 0;
+
+    return auth_line;
+}
+
+static const char *jwt_expr_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
+{
+    char *var = (char *)data;
+
+    if (var && *var && ctx->r && ap_cstr_casecmp(var, "TOKEN") == 0) {
+        return jwt_get_token(ctx->r);
+    }
+    return NULL;
+}
+
+static int jwt_expr_lookup(ap_expr_lookup_parms *parms)
+{
+    switch (parms->type) {
+    case AP_EXPR_FUNC_VAR:
+        /* for now, we just handle everything that starts with HC_.
+         */
+        if (strncasecmp(parms->name, "JWT_", 4) == 0) {
+            *parms->func = jwt_expr_var_fn;
+            *parms->data = parms->name + 4;
+            return OK;
+        }
+        break;
+    }
+    return DECLINED;
+}
+
+static apr_status_t verify_cb(apr_bucket_brigade *bb,
+        apr_jose_t *jose, apr_jose_signature_t *signature, void *ctx,
+        int *vflags, apr_pool_t *pool)
+{
+    request_rec *r = ctx;
+    apr_json_kv_t *alg = NULL;
+
+    auth_bearer_conf *sconf = ap_get_module_config(r->server->module_config,
+            &autht_jwt_module);
+
+    auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
+                                                       &autht_jwt_module);
+
+    int alg_supported = 0;
+
+    if (signature) {
+        apr_json_value_t *ph = signature->protected_header;
+
+        if (ph && ph->type == APR_JSON_OBJECT) {
+
+            alg = apr_json_object_get(ph, APR_JOSE_JWKSE_ALGORITHM,
+                    APR_JSON_VALUE_STRING);
+
+        }
+    }
+
+    if (!alg) {
+        apr_errprintf(&jose->result, r->pool, "", APR_EGENERAL,
+                "JWT token protected header has no '"
+                APR_JOSE_JWKSE_ALGORITHM
+                "' for URI '%s'",
+                r->uri);
+        return APR_EGENERAL;
+    }
+
+    if (alg->v->type != APR_JSON_STRING) {
+        apr_errprintf(&jose->result, r->pool, "", APR_EGENERAL,
+                "JWT token protected header '"
+                APR_JOSE_JWKSE_ALGORITHM
+                "' is not a string for URI '%s'",
+                r->uri);
+        return APR_EGENERAL;
+    }
+
+    /* first pass, is our algorithm supported? */
+    for (int i = 0; i < conf->verifies->nelts; i++) {
+    	auth_bearer_signature_rec *srec = &APR_ARRAY_IDX(conf->verifies,
+    			i, auth_bearer_signature_rec);
+
+    	/* which signature type do we have? */
+    	switch (srec->jws_alg) {
+    	case JWS_ALG_TYPE_NONE: {
+        	if (!strncmp(alg->v->value.string.p, "none",
+                        alg->v->value.string.len)) {
+        		alg_supported = 1;
+        	}
+        	break;
+    	}
+    	case JWS_ALG_TYPE_HS256: {
+        	if (!strncmp(alg->v->value.string.p, "HS256",
+        			alg->v->value.string.len)) {
+        		alg_supported = 1;
+        	}
+    		break;
+    	}
+    	}
+
+    }
+
+    /* we don't support the algorithm */
+    if (!alg_supported) {
+    	apr_errprintf(&jose->result, r->pool, "", APR_ENODIGEST,
+    			"JWT token protected header '"
+    			APR_JOSE_JWKSE_ALGORITHM
+				"' %s is not supported for URI '%s'",
+				alg->v->value.string.p, r->uri);
+    	return APR_ENODIGEST;
+    }
+
+    /* second pass, does the signature match? */
+    for (int i = 0; i < conf->verifies->nelts; i++) {
+    	auth_bearer_signature_rec *srec = &APR_ARRAY_IDX(conf->verifies,
+    			i, auth_bearer_signature_rec);
+
+    	/* which signature type do we have? */
+    	switch (srec->jws_alg) {
+    	case JWS_ALG_TYPE_NONE: {
+        	if (!strncmp(alg->v->value.string.p, "none",
+                        alg->v->value.string.len)) {
+        		return APR_SUCCESS;
+        	}
+        	break;
+    	}
+    	case JWS_ALG_TYPE_HS256: {
+        	if (!strncmp(alg->v->value.string.p, "HS256",
+        			alg->v->value.string.len)) {
+
+                apr_bucket *e;
+                apr_crypto_key_rec_t *krec;
+                apr_crypto_key_t *key = NULL;
+                apr_crypto_digest_t *digest = NULL;
+                apr_crypto_digest_rec_t *rec;
+                char * buf;
+                apr_status_t status;
+
+                if (!*sconf->crypto) {
+                    jose->result.msg = "token could not be verified";
+                    jose->result.reason = "no crypto driver configured (set AuthtJwtDriver)";
+                    return APR_EGENERAL;
+                }
+
+                krec = apr_crypto_key_rec_make(APR_CRYPTO_KTYPE_HMAC, pool);
+
+                krec->k.hmac.digest = APR_CRYPTO_DIGEST_SHA256;
+                krec->k.hmac.secret = srec->secret;
+                krec->k.hmac.secretLen = srec->secret_len;
+
+                status = apr_crypto_key(&key, krec, *sconf->crypto, pool);
+                if (status != APR_SUCCESS) {
+                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                    apr_strerror(status, buf, HUGE_STRING_LEN);
+                    jose->result.msg = "token could not be verified";
+                    return status;
+                }
+
+                rec = apr_crypto_digest_rec_make(APR_CRYPTO_DTYPE_SIGN, pool);
+
+                status = apr_crypto_digest_init(&digest, key, rec, pool);
+                if (status != APR_SUCCESS) {
+                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                    apr_strerror(status, buf, HUGE_STRING_LEN);
+                    jose->result.msg = "token could not be verified";
+                    return status;
+                }
+
+                for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e =
+                        APR_BUCKET_NEXT(e)) {
+                    const char *str;
+                    apr_size_t len;
+
+                    /* If we see an EOS, don't bother doing anything more. */
+                    if (APR_BUCKET_IS_EOS(e)) {
+                        break;
+                    }
+
+                    status = apr_bucket_read(e, &str, &len, APR_BLOCK_READ);
+                    if (status != APR_SUCCESS) {
+                        jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                        apr_strerror(status, buf, HUGE_STRING_LEN);
+                        jose->result.msg = "token could not be verified";
+                        return status;
+                    }
+
+                    status = apr_crypto_digest_update(digest,
+                            (const unsigned char *) str, len);
+                    if (status != APR_SUCCESS) {
+                        jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                        apr_strerror(status, buf, HUGE_STRING_LEN);
+                        jose->result.msg = "token could not be verified";
+                        return status;
+                    }
+                }
+
+                status = apr_crypto_digest_final(digest);
+                if (status != APR_SUCCESS) {
+                    jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+                    apr_strerror(status, buf, HUGE_STRING_LEN);
+                    jose->result.msg = "token could not be verified";
+                    return status;
+                }
+
+                if (signature->sig.len == rec->d.sign.slen &&
+                		!memcmp(signature->sig.data, rec->d.sign.s, rec->d.sign.slen)) {
+                    return APR_SUCCESS;
+                }
+
+        	}
+    		break;
+    	}
+    	}
+
+    }
+
+    /* no match, oh well */
+	apr_errprintf(&jose->result, r->pool, "", APR_ENODIGEST,
+			"JWT token protected header '"
+			APR_JOSE_JWKSE_ALGORITHM
+			"' %s is not supported for URI '%s'",
+			alg->v->value.string.p, r->uri);
+    return APR_ENOVERIFY;
+}
+
+static autht_status check_token(request_rec *r, const char *type,
+                                   const char *token)
+{
+    apr_bucket_brigade *bb;
+    apr_jose_t *jose = NULL;
+    apr_json_kv_t *kv;
+    apr_status_t status;
+
+    apr_jose_cb_t cb;
+
+    apr_table_t *e = r->subprocess_env;
+
+    const char *aud = NULL;
+    const char *sub = NULL;
+
+    apr_int64_t exp;
+    apr_int64_t nbf;
+
+    int exp_set = 0;
+    int nbf_set = 0;
+
+    cb.verify = verify_cb;
+    cb.decrypt = NULL;
+    cb.ctx = r;
+
+    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+    if (token) {
+        apr_brigade_write(bb, NULL, NULL, token, strlen(token));
+    }
+
+    status = apr_jose_decode(&jose, "JWT", bb, &cb, 10, APR_JOSE_FLAG_NONE, r->pool);
+
+    if (APR_SUCCESS != status) {
+        const apu_err_t *err = apr_jose_error(jose);
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+                APLOGNO() "AuthtJwt: could not decode a JWT token for URI '%s': %s: %s",
+                r->uri, err->msg, err->reason);
+        return AUTHT_DENIED;
+    }
+
+    if (jose->type != APR_JOSE_TYPE_JWT) {
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+                APLOGNO() "AuthtJwt: JOSE token was not a JWT token for URI '%s'",
+                r->uri);
+        return AUTHT_DENIED;
+    }
+
+    /* first pass - identity sub and aud */
+    kv = apr_json_object_first(jose->jose.jwt->claims);
+    do {
+
+        /* ignore any key that isn't a string */
+        if (kv->k->type != APR_JSON_STRING) {
+            continue;
+        }
+
+        if (!strncmp("aud", kv->k->value.string.p, kv->k->value.string.len)) {
+            if (kv->v->type == APR_JSON_STRING) {
+                aud = apr_pstrndup(r->pool, kv->v->value.string.p,
+                        kv->v->value.string.len);
+            }
+        }
+
+        if (!strncmp("sub", kv->k->value.string.p, kv->k->value.string.len)) {
+            if (kv->v->type == APR_JSON_STRING) {
+                sub = apr_pstrndup(r->pool, kv->v->value.string.p,
+                        kv->v->value.string.len);
+            }
+        }
+
+        if (!strncmp("exp", kv->k->value.string.p, kv->k->value.string.len)) {
+            if (kv->v->type == APR_JSON_LONG) {
+                exp = kv->v->value.lnumber;
+                exp_set = 1;
+            }
+        }
+
+        if (!strncmp("nbf", kv->k->value.string.p, kv->k->value.string.len)) {
+            if (kv->v->type == APR_JSON_LONG) {
+                nbf = kv->v->value.lnumber;
+                nbf_set = 1;
+            }
+        }
+
+
+    } while ((kv = apr_json_object_next(jose->jose.jwt->claims, kv)));
+
+    if (!aud) {
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+                APLOGNO() "AuthtJwt: JWT token 'aud' value was missing and did not match AuthName '%s' for URI '%s'",
+                ap_auth_name(r), r->uri);
+        return AUTHT_MISMATCH;
+    }
+
+    if (strcmp(aud, ap_auth_name(r))) {
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+                APLOGNO() "AuthtJwt: JWT token 'aud' value '%s' did not match AuthName '%s' for URI '%s'",
+                aud, ap_auth_name(r), r->uri);
+        return AUTHT_MISMATCH;
+    }
+
+    if (exp_set || nbf_set) {
+        apr_time_t now = apr_time_now();
+
+        if (exp_set &&
+                exp < apr_time_sec(now)) {
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+                    APLOGNO() "AuthtJwt: JWT token is expired (%"
+                    APR_INT64_T_FMT " < %" APR_TIME_T_FMT ") for URI '%s'",
+                    exp, apr_time_sec(now), r->uri);
+            return AUTHT_EXPIRED;
+        }
+
+        if (nbf_set &&
+                nbf > apr_time_sec(now)) {
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+                    APLOGNO() "AuthtJwt: JWT token is not yet valid (%"
+                    APR_INT64_T_FMT " > %" APR_TIME_T_FMT ") for URI '%s'",
+                    nbf, apr_time_sec(now), r->uri);
+            return AUTHT_INVALID;
+        }
+    }
+
+    /* we are good at this point - accept the token */
+
+    if (sub) {
+        r->user = apr_pstrdup(r->pool, sub);
+    }
+
+    /* second pass - add all string claims to the environment, prefixed by TOKEN_ */
+    kv = apr_json_object_first(jose->jose.jwt->claims);
+    do {
+        char *key, *val;
+        int j;
+
+        /* ignore anything that isn't a string */
+        if (kv->k->type != APR_JSON_STRING || kv->v->type != APR_JSON_STRING) {
+            continue;
+        }
+
+        key = apr_psprintf(r->pool, AUTHT_PREFIX "%.*s", (int)kv->k->value.string.len, kv->k->value.string.p);
+        j = sizeof(AUTHT_PREFIX);
+        while (key[j]) {
+            if (apr_isalnum(key[j])) {
+                key[j] = apr_toupper(key[j]);
+            }
+            else {
+                key[j] = '_';
+            }
+            j++;
+        }
+
+        val = apr_pstrndup(r->pool, kv->v->value.string.p,
+                kv->v->value.string.len);
+
+        apr_table_setn(e, key, val);
+
+    } while ((kv = apr_json_object_next(jose->jose.jwt->claims, kv)));
+
+    return AUTHT_GRANTED;
+}
+
+static const autht_provider autht_jwt_provider =
+{
+    &check_token
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+    ap_register_auth_provider(p, AUTHT_PROVIDER_GROUP, "jwt",
+                              AUTHT_PROVIDER_VERSION,
+                              &autht_jwt_provider, AP_AUTH_INTERNAL_PER_CONF);
+    ap_hook_expr_lookup(jwt_expr_lookup, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_hook_post_config(auth_bearer_init, NULL, NULL, APR_HOOK_LAST);
+}
+
+AP_DECLARE_MODULE(autht_jwt) =
+{
+    STANDARD20_MODULE_STUFF,
+    create_auth_bearer_dir_config,  /* dir config creater */
+    merge_auth_bearer_dir_config,   /* dir merger --- default is to override */
+    create_auth_bearer_config,      /* server config */
+    merge_auth_bearer_config,       /* merge server config */
+    auth_bearer_cmds,               /* command apr_table_t */
+    register_hooks                  /* register hooks */
+};
Index: server/request.c
===================================================================
--- server/request.c	(revision 1875208)
+++ server/request.c	(working copy)
@@ -72,6 +72,7 @@
     APR_HOOK_LINK(post_perdir_config)
     APR_HOOK_LINK(dirwalk_stat)
     APR_HOOK_LINK(force_authn)
+    APR_HOOK_LINK(token_checker)
 )
 
 AP_IMPLEMENT_HOOK_RUN_FIRST(int,translate_name,
@@ -100,6 +101,8 @@
                             (finfo, r, wanted), AP_DECLINED)
 AP_IMPLEMENT_HOOK_RUN_FIRST(int,force_authn,
                             (request_rec *r), (r), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(int,token_checker,
+                            (request_rec *r), (r), DECLINED)
 
 static int auth_internal_per_conf = 0;
 static int auth_internal_per_conf_hooks = 0;
@@ -281,6 +284,12 @@
         switch (ap_satisfies(r)) {
         case SATISFY_ALL:
         case SATISFY_NOSPEC:
+            if ((access_status = ap_run_token_checker(r)) != OK &&
+            		access_status != DECLINED) {
+                return decl_die(access_status,
+                                "check token (with Satisfy All)", r);
+            }
+
             if ((access_status = ap_run_access_checker(r)) != OK) {
                 return decl_die(access_status,
                                 "check access (with Satisfy All)", r);
@@ -316,6 +325,14 @@
             }
             break;
         case SATISFY_ANY:
+            if ((access_status = ap_run_token_checker(r)) == OK) {
+                ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+                              "request authorized bypassing access_checker by "
+                              "token_checker hook and 'Satisfy any': %s",
+                              r->uri);
+                break;
+            }
+
             if ((access_status = ap_run_access_checker(r)) == OK) {
                 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
                               "request authorized without authentication by "
@@ -2149,6 +2166,18 @@
     ap_hook_access_checker_ex(pf, aszPre, aszSucc, nOrder);
 }
 
+AP_DECLARE(void) ap_hook_check_autht(ap_HOOK_check_user_id_t *pf,
+                                     const char * const *aszPre,
+                                     const char * const *aszSucc,
+                                     int nOrder, int type)
+{
+    if ((type & AP_AUTH_INTERNAL_MASK) == AP_AUTH_INTERNAL_PER_CONF) {
+        ++auth_internal_per_conf_hooks;
+    }
+
+    ap_hook_token_checker(pf, aszPre, aszSucc, nOrder);
+}
+
 AP_DECLARE(void) ap_hook_check_authn(ap_HOOK_check_user_id_t *pf,
                                      const char * const *aszPre,
                                      const char * const *aszSucc,
