diff --git a/Makefile b/Makefile
index 4efb955c72da97aacd8aef493c0f245b665a015a..ad33700edc42571ca002a0414ae3d80f7c8e8b0e 100644
--- a/Makefile
+++ b/Makefile
@@ -479,6 +479,7 @@ grpc_completion_queue_benchmark: $(BINDIR)/$(CONFIG)/grpc_completion_queue_bench
 grpc_completion_queue_test: $(BINDIR)/$(CONFIG)/grpc_completion_queue_test
 grpc_credentials_test: $(BINDIR)/$(CONFIG)/grpc_credentials_test
 grpc_fetch_oauth2: $(BINDIR)/$(CONFIG)/grpc_fetch_oauth2
+grpc_create_jwt: $(BINDIR)/$(CONFIG)/grpc_create_jwt
 grpc_json_token_test: $(BINDIR)/$(CONFIG)/grpc_json_token_test
 grpc_stream_op_test: $(BINDIR)/$(CONFIG)/grpc_stream_op_test
 hpack_parser_test: $(BINDIR)/$(CONFIG)/hpack_parser_test
@@ -1762,7 +1763,7 @@ test_cxx: buildtests_cxx
 	$(Q) $(BINDIR)/$(CONFIG)/thread_pool_test || ( echo test thread_pool_test failed ; exit 1 )
 
 
-tools: privatelibs $(BINDIR)/$(CONFIG)/gen_hpack_tables $(BINDIR)/$(CONFIG)/grpc_fetch_oauth2
+tools: privatelibs $(BINDIR)/$(CONFIG)/gen_hpack_tables $(BINDIR)/$(CONFIG)/grpc_fetch_oauth2 $(BINDIR)/$(CONFIG)/grpc_create_jwt
 
 buildbenchmarks: privatelibs $(BINDIR)/$(CONFIG)/grpc_completion_queue_benchmark $(BINDIR)/$(CONFIG)/low_level_ping_pong_benchmark
 
@@ -6478,6 +6479,37 @@ endif
 endif
 
 
+GRPC_CREATE_JWT_SRC = \
+    test/core/security/create_jwt.c \
+
+GRPC_CREATE_JWT_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(GRPC_CREATE_JWT_SRC))))
+
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL with ALPN.
+
+$(BINDIR)/$(CONFIG)/grpc_create_jwt: openssl_dep_error
+
+else
+
+$(BINDIR)/$(CONFIG)/grpc_create_jwt: $(GRPC_CREATE_JWT_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LD) $(LDFLAGS) $(GRPC_CREATE_JWT_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o $(BINDIR)/$(CONFIG)/grpc_create_jwt
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/core/security/create_jwt.o:  $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_grpc_create_jwt: $(GRPC_CREATE_JWT_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(GRPC_CREATE_JWT_OBJS:.o=.dep)
+endif
+endif
+
+
 GRPC_JSON_TOKEN_TEST_SRC = \
     test/core/security/json_token_test.c \
 
diff --git a/build.json b/build.json
index 002cabe2bc859e4f4cf87da7b7a319fd6aae4bb0..ac80956fb85ebd1f76a785f394c66e62289c9022 100644
--- a/build.json
+++ b/build.json
@@ -1157,6 +1157,20 @@
         "gpr"
       ]
     },
+    {
+      "name": "grpc_create_jwt",
+      "build": "tool",
+      "language": "c",
+      "src": [
+        "test/core/security/create_jwt.c"
+      ],
+      "deps": [
+        "grpc_test_util",
+        "grpc",
+        "gpr_test_util",
+        "gpr"
+      ]
+    },
     {
       "name": "grpc_json_token_test",
       "build": "test",
diff --git a/include/grpc/grpc_security.h b/include/grpc/grpc_security.h
index f03ac8004dad373438e4accd21aae7a8bb0f1710..472887f7c6b4a05dcb8edf14941d4632bd4f454e 100644
--- a/include/grpc/grpc_security.h
+++ b/include/grpc/grpc_security.h
@@ -100,6 +100,14 @@ extern const gpr_timespec grpc_max_auth_token_lifetime;
 grpc_credentials *grpc_service_account_credentials_create(
     const char *json_key, const char *scope, gpr_timespec token_lifetime);
 
+/* Creates a JWT credentials object. May return NULL if the input is invalid.
+   - json_key is the JSON key string containing the client's private key.
+   - token_lifetime is the lifetime of each Json Web Token (JWT) created with
+     this credentials.  It should not exceed grpc_max_auth_token_lifetime or
+     will be cropped to this value.  */
+grpc_credentials *grpc_jwt_credentials_create(const char *json_key,
+                                              gpr_timespec token_lifetime);
+
 /* Creates a fake transport security credentials object for testing. */
 grpc_credentials *grpc_fake_transport_security_credentials_create(void);
 
diff --git a/src/core/security/auth.c b/src/core/security/auth.c
index 58679a87aa5509b653f08d4503fd37d34e7a7d97..92878e3b7e3ce5c8a91954acf1d1b239eed1cd04 100644
--- a/src/core/security/auth.c
+++ b/src/core/security/auth.c
@@ -48,6 +48,7 @@
 typedef struct {
   grpc_credentials *creds;
   grpc_mdstr *host;
+  grpc_mdstr *method;
   grpc_call_op op;
 } call_data;
 
@@ -56,6 +57,7 @@ typedef struct {
   grpc_channel_security_context *security_context;
   grpc_mdctx *md_ctx;
   grpc_mdstr *authority_string;
+  grpc_mdstr *path_string;
   grpc_mdstr *error_msg_key;
 } channel_data;
 
@@ -89,6 +91,26 @@ static void on_credentials_metadata(void *user_data, grpc_mdelem **md_elems,
   grpc_call_next_op(elem, &((call_data *)elem->call_data)->op);
 }
 
+static char *build_service_url(const char *url_scheme, call_data *calld) {
+  char *service_url;
+  char *service = gpr_strdup(grpc_mdstr_as_c_string(calld->method));
+  char *last_slash = strrchr(service, '/');
+  if (last_slash == NULL) {
+    gpr_log(GPR_ERROR, "No '/' found in fully qualified method name");
+    service[0] = '\0';
+  } else if (last_slash == service) {
+    /* No service part in fully qualified method name: will just be "/". */
+    service[1] = '\0';
+  } else {
+    *last_slash = '\0';
+  }
+  if (url_scheme == NULL) url_scheme = "";
+  gpr_asprintf(&service_url, "%s://%s%s", url_scheme,
+               grpc_mdstr_as_c_string(calld->host), service);
+  gpr_free(service);
+  return service_url;
+}
+
 static void send_security_metadata(grpc_call_element *elem, grpc_call_op *op) {
   /* grab pointers to our data from the call element */
   call_data *calld = elem->call_data;
@@ -106,9 +128,12 @@ static void send_security_metadata(grpc_call_element *elem, grpc_call_op *op) {
   }
   if (channel_creds != NULL &&
       grpc_credentials_has_request_metadata(channel_creds)) {
+    char *service_url =
+        build_service_url(channeld->security_context->base.url_scheme, calld);
     calld->op = *op; /* Copy op (originates from the caller's stack). */
-    grpc_credentials_get_request_metadata(channel_creds,
+    grpc_credentials_get_request_metadata(channel_creds, service_url,
                                           on_credentials_metadata, elem);
+    gpr_free(service_url);
   } else {
     grpc_call_next_op(elem, op);
   }
@@ -146,6 +171,9 @@ static void call_op(grpc_call_element *elem, grpc_call_element *from_elem,
       if (op->data.metadata->key == channeld->authority_string) {
         if (calld->host != NULL) grpc_mdstr_unref(calld->host);
         calld->host = grpc_mdstr_ref(op->data.metadata->value);
+      } else if (op->data.metadata->key == channeld->path_string) {
+        if (calld->method != NULL) grpc_mdstr_unref(calld->method);
+        calld->method = grpc_mdstr_ref(op->data.metadata->value);
       }
       grpc_call_next_op(elem, op);
       break;
@@ -194,6 +222,7 @@ static void init_call_elem(grpc_call_element *elem,
   call_data *calld = elem->call_data;
   calld->creds = NULL;
   calld->host = NULL;
+  calld->method = NULL;
 }
 
 /* Destructor for call_data */
@@ -230,6 +259,7 @@ static void init_channel_elem(grpc_channel_element *elem,
   channeld->md_ctx = metadata_context;
   channeld->authority_string =
       grpc_mdstr_from_string(channeld->md_ctx, ":authority");
+  channeld->path_string = grpc_mdstr_from_string(channeld->md_ctx, ":path");
   channeld->error_msg_key =
       grpc_mdstr_from_string(channeld->md_ctx, "grpc-message");
 }
diff --git a/src/core/security/credentials.c b/src/core/security/credentials.c
index a21c27bff946bef47ae9a2291fd7e8de9da8a8af..42d1a900fc52b69187a02b31033e18e4903e0898 100644
--- a/src/core/security/credentials.c
+++ b/src/core/security/credentials.c
@@ -33,6 +33,10 @@
 
 #include "src/core/security/credentials.h"
 
+#include <string.h>
+#include <stdio.h>
+
+#include "src/core/json/json.h"
 #include "src/core/httpcli/httpcli.h"
 #include "src/core/iomgr/iomgr.h"
 #include "src/core/security/json_token.h"
@@ -42,14 +46,9 @@
 #include <grpc/support/sync.h>
 #include <grpc/support/time.h>
 
-#include "src/core/json/json.h"
-
-#include <string.h>
-#include <stdio.h>
-
 /* -- Constants. -- */
 
-#define GRPC_OAUTH2_TOKEN_REFRESH_THRESHOLD_SECS 60
+#define GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS 60
 
 #define GRPC_COMPUTE_ENGINE_METADATA_HOST "metadata"
 #define GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH \
@@ -113,6 +112,7 @@ int grpc_credentials_has_request_metadata_only(grpc_credentials *creds) {
 }
 
 void grpc_credentials_get_request_metadata(grpc_credentials *creds,
+                                           const char *service_url,
                                            grpc_credentials_metadata_cb cb,
                                            void *user_data) {
   if (creds == NULL || !grpc_credentials_has_request_metadata(creds) ||
@@ -122,7 +122,7 @@ void grpc_credentials_get_request_metadata(grpc_credentials *creds,
     }
     return;
   }
-  creds->vtable->get_request_metadata(creds, cb, user_data);
+  creds->vtable->get_request_metadata(creds, service_url, cb, user_data);
 }
 
 void grpc_server_credentials_release(grpc_server_credentials *creds) {
@@ -288,6 +288,128 @@ grpc_server_credentials *grpc_ssl_server_credentials_create(
   return &c->base;
 }
 
+/* -- Jwt credentials -- */
+
+typedef struct {
+  grpc_credentials base;
+  grpc_mdctx *md_ctx;
+
+  /* Have a simple cache for now with just 1 entry. We could have a map based on
+     the service_url for a more sophisticated one. */
+  gpr_mu cache_mu;
+  struct {
+    grpc_mdelem *jwt_md;
+    char *service_url;
+    gpr_timespec jwt_expiration;
+  } cached;
+
+  grpc_auth_json_key key;
+  gpr_timespec jwt_lifetime;
+} grpc_jwt_credentials;
+
+static void jwt_reset_cache(grpc_jwt_credentials *c) {
+  if (c->cached.jwt_md != NULL) {
+    grpc_mdelem_unref(c->cached.jwt_md);
+    c->cached.jwt_md = NULL;
+  }
+  if (c->cached.service_url != NULL) {
+    gpr_free(c->cached.service_url);
+    c->cached.service_url = NULL;
+  }
+  c->cached.jwt_expiration = gpr_inf_past;
+}
+
+static void jwt_destroy(grpc_credentials *creds) {
+  grpc_jwt_credentials *c = (grpc_jwt_credentials *)creds;
+  grpc_auth_json_key_destruct(&c->key);
+  jwt_reset_cache(c);
+  gpr_mu_destroy(&c->cache_mu);
+  grpc_mdctx_unref(c->md_ctx);
+  gpr_free(c);
+}
+
+static int jwt_has_request_metadata(const grpc_credentials *creds) { return 1; }
+
+static int jwt_has_request_metadata_only(const grpc_credentials *creds) {
+  return 1;
+}
+
+
+static void jwt_get_request_metadata(grpc_credentials *creds,
+                                     const char *service_url,
+                                     grpc_credentials_metadata_cb cb,
+                                     void *user_data) {
+  grpc_jwt_credentials *c = (grpc_jwt_credentials *)creds;
+  gpr_timespec refresh_threshold = {GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS,
+                                    0};
+
+  /* See if we can return a cached jwt. */
+  grpc_mdelem *jwt_md = NULL;
+  {
+    gpr_mu_lock(&c->cache_mu);
+    if (c->cached.service_url != NULL &&
+        !strcmp(c->cached.service_url, service_url) &&
+        c->cached.jwt_md != NULL &&
+        (gpr_time_cmp(gpr_time_sub(c->cached.jwt_expiration, gpr_now()),
+                      refresh_threshold) > 0)) {
+      jwt_md = grpc_mdelem_ref(c->cached.jwt_md);
+    }
+    gpr_mu_unlock(&c->cache_mu);
+  }
+
+  if (jwt_md == NULL) {
+    char *jwt = NULL;
+    /* Generate a new jwt. */
+    gpr_mu_lock(&c->cache_mu);
+    jwt_reset_cache(c);
+    jwt = grpc_jwt_encode_and_sign(&c->key, service_url, c->jwt_lifetime, NULL);
+    if (jwt != NULL) {
+      char *md_value;
+      gpr_asprintf(&md_value, "Bearer %s", jwt);
+      gpr_free(jwt);
+      c->cached.jwt_expiration = gpr_time_add(gpr_now(), c->jwt_lifetime);
+      c->cached.service_url = gpr_strdup(service_url);
+      c->cached.jwt_md = grpc_mdelem_from_strings(
+          c->md_ctx, GRPC_AUTHORIZATION_METADATA_KEY, md_value);
+      gpr_free(md_value);
+      jwt_md = grpc_mdelem_ref(c->cached.jwt_md);
+    }
+    gpr_mu_unlock(&c->cache_mu);
+  }
+
+  if (jwt_md != NULL) {
+    cb(user_data, &jwt_md, 1, GRPC_CREDENTIALS_OK);
+    grpc_mdelem_unref(jwt_md);
+  } else {
+    cb(user_data, NULL, 0, GRPC_CREDENTIALS_ERROR);
+  }
+}
+
+static grpc_credentials_vtable jwt_vtable = {
+    jwt_destroy, jwt_has_request_metadata, jwt_has_request_metadata_only,
+    jwt_get_request_metadata};
+
+grpc_credentials *grpc_jwt_credentials_create(const char *json_key,
+                                              gpr_timespec token_lifetime) {
+  grpc_jwt_credentials *c;
+  grpc_auth_json_key key = grpc_auth_json_key_create_from_string(json_key);
+  if (!grpc_auth_json_key_is_valid(&key)) {
+    gpr_log(GPR_ERROR, "Invalid input for jwt credentials creation");
+    return NULL;
+  }
+  c = gpr_malloc(sizeof(grpc_jwt_credentials));
+  memset(c, 0, sizeof(grpc_jwt_credentials));
+  c->base.type = GRPC_CREDENTIALS_TYPE_JWT;
+  gpr_ref_init(&c->base.refcount, 1);
+  c->base.vtable = &jwt_vtable;
+  c->md_ctx = grpc_mdctx_create();
+  c->key = key;
+  c->jwt_lifetime = token_lifetime;
+  gpr_mu_init(&c->cache_mu);
+  jwt_reset_cache(c);
+  return &c->base;
+}
+
 /* -- Oauth2TokenFetcher credentials -- */
 
 /* This object is a base for credentials that need to acquire an oauth2 token
@@ -439,10 +561,11 @@ static void on_oauth2_token_fetcher_http_response(
 }
 
 static void oauth2_token_fetcher_get_request_metadata(
-    grpc_credentials *creds, grpc_credentials_metadata_cb cb, void *user_data) {
+    grpc_credentials *creds, const char *service_url,
+    grpc_credentials_metadata_cb cb, void *user_data) {
   grpc_oauth2_token_fetcher_credentials *c =
       (grpc_oauth2_token_fetcher_credentials *)creds;
-  gpr_timespec refresh_threshold = {GRPC_OAUTH2_TOKEN_REFRESH_THRESHOLD_SECS,
+  gpr_timespec refresh_threshold = {GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS,
                                     0};
   grpc_mdelem *cached_access_token_md = NULL;
   {
@@ -535,7 +658,8 @@ static void service_account_fetch_oauth2(
                                 "application/x-www-form-urlencoded"};
   grpc_httpcli_request request;
   char *body = NULL;
-  char *jwt = grpc_jwt_encode_and_sign(&c->key, c->scope, c->token_lifetime);
+  char *jwt = grpc_jwt_encode_and_sign(&c->key, GRPC_JWT_OAUTH2_AUDIENCE,
+                                       c->token_lifetime, c->scope);
   if (jwt == NULL) {
     grpc_httpcli_response response;
     memset(&response, 0, sizeof(grpc_httpcli_response));
@@ -616,6 +740,7 @@ void on_simulated_token_fetch_done(void *user_data, int success) {
 }
 
 static void fake_oauth2_get_request_metadata(grpc_credentials *creds,
+                                             const char *service_url,
                                              grpc_credentials_metadata_cb cb,
                                              void *user_data) {
   grpc_fake_oauth2_credentials *c = (grpc_fake_oauth2_credentials *)creds;
@@ -709,6 +834,7 @@ typedef struct {
   size_t creds_index;
   grpc_mdelem **md_elems;
   size_t num_md;
+  char *service_url;
   void *user_data;
   grpc_credentials_metadata_cb cb;
 } grpc_composite_credentials_metadata_context;
@@ -754,6 +880,7 @@ static void composite_md_context_destroy(
     grpc_mdelem_unref(ctx->md_elems[i]);
   }
   gpr_free(ctx->md_elems);
+  if (ctx->service_url != NULL) gpr_free(ctx->service_url);
   gpr_free(ctx);
 }
 
@@ -783,8 +910,8 @@ static void composite_metadata_cb(void *user_data, grpc_mdelem **md_elems,
     grpc_credentials *inner_creds =
         ctx->composite_creds->inner.creds_array[ctx->creds_index++];
     if (grpc_credentials_has_request_metadata(inner_creds)) {
-      grpc_credentials_get_request_metadata(inner_creds, composite_metadata_cb,
-                                            ctx);
+      grpc_credentials_get_request_metadata(inner_creds, ctx->service_url,
+                                            composite_metadata_cb, ctx);
       return;
     }
   }
@@ -795,6 +922,7 @@ static void composite_metadata_cb(void *user_data, grpc_mdelem **md_elems,
 }
 
 static void composite_get_request_metadata(grpc_credentials *creds,
+                                           const char *service_url,
                                            grpc_credentials_metadata_cb cb,
                                            void *user_data) {
   grpc_composite_credentials *c = (grpc_composite_credentials *)creds;
@@ -805,14 +933,15 @@ static void composite_get_request_metadata(grpc_credentials *creds,
   }
   ctx = gpr_malloc(sizeof(grpc_composite_credentials_metadata_context));
   memset(ctx, 0, sizeof(grpc_composite_credentials_metadata_context));
+  ctx->service_url = gpr_strdup(service_url);
   ctx->user_data = user_data;
   ctx->cb = cb;
   ctx->composite_creds = c;
   while (ctx->creds_index < c->inner.num_creds) {
     grpc_credentials *inner_creds = c->inner.creds_array[ctx->creds_index++];
     if (grpc_credentials_has_request_metadata(inner_creds)) {
-      grpc_credentials_get_request_metadata(inner_creds, composite_metadata_cb,
-                                            ctx);
+      grpc_credentials_get_request_metadata(inner_creds, service_url,
+                                            composite_metadata_cb, ctx);
       return;
     }
   }
@@ -916,6 +1045,7 @@ static int iam_has_request_metadata_only(const grpc_credentials *creds) {
 }
 
 static void iam_get_request_metadata(grpc_credentials *creds,
+                                     const char *service_url,
                                      grpc_credentials_metadata_cb cb,
                                      void *user_data) {
   grpc_iam_credentials *c = (grpc_iam_credentials *)creds;
diff --git a/src/core/security/credentials.h b/src/core/security/credentials.h
index 614db96ad7ac705617e513f4307a42c7cd040742..7b8929492b24d73731860f5c700afb6fbf100d98 100644
--- a/src/core/security/credentials.h
+++ b/src/core/security/credentials.h
@@ -50,6 +50,7 @@ typedef enum {
 
 #define GRPC_CREDENTIALS_TYPE_SSL "Ssl"
 #define GRPC_CREDENTIALS_TYPE_OAUTH2 "Oauth2"
+#define GRPC_CREDENTIALS_TYPE_JWT "Jwt"
 #define GRPC_CREDENTIALS_TYPE_IAM "Iam"
 #define GRPC_CREDENTIALS_TYPE_COMPOSITE "Composite"
 #define GRPC_CREDENTIALS_TYPE_FAKE_TRANSPORT_SECURITY "FakeTransportSecurity"
@@ -71,6 +72,7 @@ typedef struct {
   int (*has_request_metadata)(const grpc_credentials *c);
   int (*has_request_metadata_only)(const grpc_credentials *c);
   void (*get_request_metadata)(grpc_credentials *c,
+                               const char *service_url,
                                grpc_credentials_metadata_cb cb,
                                void *user_data);
 } grpc_credentials_vtable;
@@ -86,6 +88,7 @@ void grpc_credentials_unref(grpc_credentials *creds);
 int grpc_credentials_has_request_metadata(grpc_credentials *creds);
 int grpc_credentials_has_request_metadata_only(grpc_credentials *creds);
 void grpc_credentials_get_request_metadata(grpc_credentials *creds,
+                                           const char *service_url,
                                            grpc_credentials_metadata_cb cb,
                                            void *user_data);
 typedef struct {
diff --git a/src/core/security/json_token.c b/src/core/security/json_token.c
index 20d62e2b438f4f2cce653c1cad128b98db55d597..26d57036a6d58d66b968faf499960fdf0f14ac0a 100644
--- a/src/core/security/json_token.c
+++ b/src/core/security/json_token.c
@@ -55,7 +55,6 @@ const gpr_timespec grpc_max_auth_token_lifetime = {3600, 0};
 #define GRPC_AUTH_JSON_KEY_TYPE_INVALID "invalid"
 #define GRPC_AUTH_JSON_KEY_TYPE_SERVICE_ACCOUNT "service_account"
 
-#define GRPC_JWT_AUDIENCE "https://www.googleapis.com/oauth2/v3/token"
 #define GRPC_JWT_RSA_SHA256_ALGORITHM "RS256"
 #define GRPC_JWT_TYPE "JWT"
 
@@ -171,8 +170,8 @@ void grpc_auth_json_key_destruct(grpc_auth_json_key *json_key) {
 /* --- jwt encoding and signature. --- */
 
 static grpc_json *create_child(grpc_json *brother, grpc_json *parent,
-                         const char *key, const char *value,
-                         grpc_json_type type) {
+                               const char *key, const char *value,
+                               grpc_json_type type) {
   grpc_json *child = grpc_json_create(type);
   if (brother) brother->next = child;
   if (!parent->child) parent->child = child;
@@ -182,14 +181,15 @@ static grpc_json *create_child(grpc_json *brother, grpc_json *parent,
   return child;
 }
 
-static char *encoded_jwt_header(const char *algorithm) {
+static char *encoded_jwt_header(const char *key_id, const char *algorithm) {
   grpc_json *json = grpc_json_create(GRPC_JSON_OBJECT);
   grpc_json *child = NULL;
   char *json_str = NULL;
   char *result = NULL;
 
   child = create_child(NULL, json, "alg", algorithm, GRPC_JSON_STRING);
-  create_child(child, json, "typ", GRPC_JWT_TYPE, GRPC_JSON_STRING);
+  child = create_child(child, json, "typ", GRPC_JWT_TYPE, GRPC_JSON_STRING);
+  create_child(child, json, "kid", key_id, GRPC_JSON_STRING);
 
   json_str = grpc_json_dump_to_string(json, 0);
   result = grpc_base64_encode(json_str, strlen(json_str), 1, 0);
@@ -199,7 +199,8 @@ static char *encoded_jwt_header(const char *algorithm) {
 }
 
 static char *encoded_jwt_claim(const grpc_auth_json_key *json_key,
-                               const char *scope, gpr_timespec token_lifetime) {
+                               const char *audience,
+                               gpr_timespec token_lifetime, const char *scope) {
   grpc_json *json = grpc_json_create(GRPC_JSON_OBJECT);
   grpc_json *child = NULL;
   char *json_str = NULL;
@@ -217,8 +218,15 @@ static char *encoded_jwt_claim(const grpc_auth_json_key *json_key,
 
   child = create_child(NULL, json, "iss", json_key->client_email,
                        GRPC_JSON_STRING);
-  child = create_child(child, json, "scope", scope, GRPC_JSON_STRING);
-  child = create_child(child, json, "aud", GRPC_JWT_AUDIENCE, GRPC_JSON_STRING);
+  if (scope != NULL) {
+    child = create_child(child, json, "scope", scope, GRPC_JSON_STRING);
+  } else {
+    /* Unscoped JWTs need a sub field. */
+    child = create_child(child, json, "sub", json_key->client_email,
+                         GRPC_JSON_STRING);
+  }
+
+  child = create_child(child, json, "aud", audience, GRPC_JSON_STRING);
   child = create_child(child, json, "iat", now_str, GRPC_JSON_NUMBER);
   create_child(child, json, "exp", expiration_str, GRPC_JSON_NUMBER);
 
@@ -300,14 +308,16 @@ end:
 }
 
 char *grpc_jwt_encode_and_sign(const grpc_auth_json_key *json_key,
-                               const char *scope, gpr_timespec token_lifetime) {
+                               const char *audience,
+                               gpr_timespec token_lifetime, const char *scope) {
   if (g_jwt_encode_and_sign_override != NULL) {
-    return g_jwt_encode_and_sign_override(json_key, scope, token_lifetime);
+    return g_jwt_encode_and_sign_override(json_key, audience, token_lifetime,
+                                          scope);
   } else {
     const char *sig_algo = GRPC_JWT_RSA_SHA256_ALGORITHM;
     char *to_sign = dot_concat_and_free_strings(
-        encoded_jwt_header(sig_algo),
-        encoded_jwt_claim(json_key, scope, token_lifetime));
+        encoded_jwt_header(json_key->private_key_id, sig_algo),
+        encoded_jwt_claim(json_key, audience, token_lifetime, scope));
     char *sig = compute_and_encode_signature(json_key, sig_algo, to_sign);
     if (sig == NULL) {
       gpr_free(to_sign);
diff --git a/src/core/security/json_token.h b/src/core/security/json_token.h
index 5a9b2dab4b3df82f48f153be865a5e23d5b6f64d..1ef9682f528cbcb78c77e2b2f7f0c0458ea85ca1 100644
--- a/src/core/security/json_token.h
+++ b/src/core/security/json_token.h
@@ -37,6 +37,10 @@
 #include <grpc/support/slice.h>
 #include <openssl/rsa.h>
 
+/* --- Constants. --- */
+
+#define GRPC_JWT_OAUTH2_AUDIENCE "https://www.googleapis.com/oauth2/v3/token"
+
 /* --- auth_json_key parsing. --- */
 
 typedef struct {
@@ -61,14 +65,15 @@ void grpc_auth_json_key_destruct(grpc_auth_json_key *json_key);
 /* --- json token encoding and signing. --- */
 
 /* Caller is responsible for calling gpr_free on the returned value. May return
-   NULL on invalid input. */
+   NULL on invalid input. The scope parameter may be NULL. */
 char *grpc_jwt_encode_and_sign(const grpc_auth_json_key *json_key,
-                               const char *scope, gpr_timespec token_lifetime);
+                               const char *audience,
+                               gpr_timespec token_lifetime, const char *scope);
 
 /* Override encode_and_sign function for testing. */
 typedef char *(*grpc_jwt_encode_and_sign_override)(
-    const grpc_auth_json_key *json_key, const char *scope,
-    gpr_timespec token_lifetime);
+    const grpc_auth_json_key *json_key, const char *audience,
+    gpr_timespec token_lifetime, const char *scope);
 
 /* Set a custom encode_and_sign override for testing. */
 void grpc_jwt_encode_and_sign_set_override(
diff --git a/src/core/security/security_context.c b/src/core/security/security_context.c
index 37a312bc812ad0d62858d04fc837e7442b4c39c6..71bd06b271dc56731f8716bc994f04ead8c2904e 100644
--- a/src/core/security/security_context.c
+++ b/src/core/security/security_context.c
@@ -238,6 +238,7 @@ grpc_channel_security_context *grpc_fake_channel_security_context_create(
       gpr_malloc(sizeof(grpc_fake_channel_security_context));
   gpr_ref_init(&c->base.base.refcount, 1);
   c->base.base.is_client_side = 1;
+  c->base.base.url_scheme = GRPC_FAKE_SECURITY_URL_SCHEME;
   c->base.base.vtable = &fake_channel_vtable;
   GPR_ASSERT(check_request_metadata_creds(request_metadata_creds));
   c->base.request_metadata_creds = grpc_credentials_ref(request_metadata_creds);
@@ -250,6 +251,7 @@ grpc_security_context *grpc_fake_server_security_context_create(void) {
   grpc_security_context *c = gpr_malloc(sizeof(grpc_security_context));
   gpr_ref_init(&c->refcount, 1);
   c->vtable = &fake_server_vtable;
+  c->url_scheme = GRPC_FAKE_SECURITY_URL_SCHEME;
   return c;
 }
 
@@ -458,6 +460,7 @@ grpc_security_status grpc_ssl_channel_security_context_create(
   gpr_ref_init(&c->base.base.refcount, 1);
   c->base.base.vtable = &ssl_channel_vtable;
   c->base.base.is_client_side = 1;
+  c->base.base.url_scheme = GRPC_SSL_URL_SCHEME;
   c->base.request_metadata_creds = grpc_credentials_ref(request_metadata_creds);
   c->base.check_call_host = ssl_channel_check_call_host;
   if (target_name != NULL) {
@@ -525,6 +528,7 @@ grpc_security_status grpc_ssl_server_security_context_create(
   memset(c, 0, sizeof(grpc_ssl_server_security_context));
 
   gpr_ref_init(&c->base.refcount, 1);
+  c->base.url_scheme = GRPC_SSL_URL_SCHEME;
   c->base.vtable = &ssl_server_vtable;
   result = tsi_create_ssl_server_handshaker_factory(
       (const unsigned char **)config->pem_private_keys,
diff --git a/src/core/security/security_context.h b/src/core/security/security_context.h
index 2b9610fac3168c8ef540909993cbd18841b0c2f2..40e2daceb80d2b816d523852bffe4117dd593d63 100644
--- a/src/core/security/security_context.h
+++ b/src/core/security/security_context.h
@@ -47,6 +47,11 @@ typedef enum {
   GRPC_SECURITY_ERROR
 } grpc_security_status;
 
+/* --- URL schemes. --- */
+
+#define GRPC_SSL_URL_SCHEME "https"
+#define GRPC_FAKE_SECURITY_URL_SCHEME "http+fake_security"
+
 /* --- security_context object. ---
 
     A security context object represents away to configure the underlying
@@ -72,6 +77,7 @@ struct grpc_security_context {
   const grpc_security_context_vtable *vtable;
   gpr_refcount refcount;
   int is_client_side;
+  const char *url_scheme;
 };
 
 /* Increments the refcount. */
diff --git a/test/core/security/create_jwt.c b/test/core/security/create_jwt.c
new file mode 100644
index 0000000000000000000000000000000000000000..614dd1e50c66ad5126db9e21a7cc8bfc7cee70dc
--- /dev/null
+++ b/test/core/security/create_jwt.c
@@ -0,0 +1,110 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "src/core/security/credentials.h"
+#include "src/core/security/json_token.h"
+#include "src/core/support/file.h"
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/cmdline.h>
+#include <grpc/support/log.h>
+#include <grpc/support/slice.h>
+
+void create_jwt(const char *json_key_file_path, const char *service_url,
+                const char *scope) {
+  grpc_auth_json_key key;
+  int ok = 0;
+  char *jwt;
+  gpr_slice json_key_data = gpr_load_file(json_key_file_path, &ok);
+  if (!ok) {
+    fprintf(stderr, "Could not read %s.\n", json_key_file_path);
+    exit(1);
+  }
+  key = grpc_auth_json_key_create_from_string(
+      (const char *)GPR_SLICE_START_PTR(json_key_data));
+  gpr_slice_unref(json_key_data);
+  if (!grpc_auth_json_key_is_valid(&key)) {
+    fprintf(stderr, "Could not parse json key.\n");
+    exit(1);
+  }
+  jwt = grpc_jwt_encode_and_sign(
+      &key, service_url == NULL ? GRPC_JWT_OAUTH2_AUDIENCE : service_url,
+      grpc_max_auth_token_lifetime, scope);
+  grpc_auth_json_key_destruct(&key);
+  if (jwt == NULL) {
+    fprintf(stderr, "Could not create JWT.\n");
+    exit(1);
+  }
+  fprintf(stdout, "%s\n", jwt);
+  gpr_free(jwt);
+}
+
+int main(int argc, char **argv) {
+  char *scope = NULL;
+  char *json_key_file_path = NULL;
+  char *service_url = NULL;
+  gpr_cmdline *cl = gpr_cmdline_create("create_jwt");
+  gpr_cmdline_add_string(cl, "json_key", "File path of the json key.",
+                         &json_key_file_path);
+  gpr_cmdline_add_string(cl, "scope",
+                         "OPTIONAL Space delimited permissions. Mutually "
+                         "exclusive with service_url",
+                         &scope);
+  gpr_cmdline_add_string(cl, "service_url",
+                         "OPTIONAL service URL. Mutually exclusive with scope.",
+                         &service_url);
+  gpr_cmdline_parse(cl, argc, argv);
+
+  if (json_key_file_path == NULL) {
+    fprintf(stderr, "Missing --json_key option.\n");
+    exit(1);
+  }
+  if (scope != NULL) {
+    if (service_url != NULL) {
+      fprintf(stderr,
+              "Options --scope and --service_url are mutually exclusive.\n");
+      exit(1);
+    }
+  } else if (service_url == NULL) {
+    fprintf(stderr, "Need one of --service_url or --scope options.\n");
+    exit(1);
+  }
+
+  create_jwt(json_key_file_path, service_url, scope);
+
+  gpr_cmdline_destroy(cl);
+  return 0;
+}
diff --git a/test/core/security/credentials_test.c b/test/core/security/credentials_test.c
index f911db6de1f33174029451b8103d78782e660692..91229c95c25115c7f7fde5be7a9fa3ffd2c40768 100644
--- a/test/core/security/credentials_test.c
+++ b/test/core/security/credentials_test.c
@@ -89,12 +89,17 @@ static const char test_user_data[] = "user data";
 
 static const char test_scope[] = "perm1 perm2";
 
-static const char test_signed_jwt[] = "signed jwt";
+static const char test_signed_jwt[] =
+    "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImY0OTRkN2M1YWU2MGRmOTcyNmM4YW"
+    "U0MDcyZTViYTdmZDkwODg2YzcifQ";
 
 static const char expected_service_account_http_body_prefix[] =
     "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&"
     "assertion=";
 
+static const char test_service_url[] = "https://foo.com/foo.v1";
+static const char other_test_service_url[] = "https://bar.com/bar.v1";
+
 static char *test_json_key_str(void) {
   size_t result_len = strlen(test_json_key_str_part1) +
                       strlen(test_json_key_str_part2) +
@@ -259,7 +264,8 @@ static void test_iam_creds(void) {
       test_iam_authorization_token, test_iam_authority_selector);
   GPR_ASSERT(grpc_credentials_has_request_metadata(creds));
   GPR_ASSERT(grpc_credentials_has_request_metadata_only(creds));
-  grpc_credentials_get_request_metadata(creds, check_iam_metadata, creds);
+  grpc_credentials_get_request_metadata(creds, test_service_url,
+                                        check_iam_metadata, creds);
 }
 
 static void check_ssl_oauth2_composite_metadata(
@@ -293,8 +299,9 @@ static void test_ssl_oauth2_composite_creds(void) {
       !strcmp(creds_array->creds_array[0]->type, GRPC_CREDENTIALS_TYPE_SSL));
   GPR_ASSERT(
       !strcmp(creds_array->creds_array[1]->type, GRPC_CREDENTIALS_TYPE_OAUTH2));
-  grpc_credentials_get_request_metadata(
-      composite_creds, check_ssl_oauth2_composite_metadata, composite_creds);
+  grpc_credentials_get_request_metadata(composite_creds, test_service_url,
+                                        check_ssl_oauth2_composite_metadata,
+                                        composite_creds);
 }
 
 static void check_ssl_oauth2_iam_composite_metadata(
@@ -338,7 +345,7 @@ static void test_ssl_oauth2_iam_composite_creds(void) {
       !strcmp(creds_array->creds_array[1]->type, GRPC_CREDENTIALS_TYPE_OAUTH2));
   GPR_ASSERT(
       !strcmp(creds_array->creds_array[2]->type, GRPC_CREDENTIALS_TYPE_IAM));
-  grpc_credentials_get_request_metadata(composite_creds,
+  grpc_credentials_get_request_metadata(composite_creds, test_service_url,
                                         check_ssl_oauth2_iam_composite_metadata,
                                         composite_creds);
 }
@@ -420,14 +427,14 @@ static void test_compute_engine_creds_success(void) {
   /* First request: http get should be called. */
   grpc_httpcli_set_override(compute_engine_httpcli_get_success_override,
                             httpcli_post_should_not_be_called);
-  grpc_credentials_get_request_metadata(compute_engine_creds,
+  grpc_credentials_get_request_metadata(compute_engine_creds, test_service_url,
                                         on_oauth2_creds_get_metadata_success,
                                         (void *)test_user_data);
 
   /* Second request: the cached token should be served directly. */
   grpc_httpcli_set_override(httpcli_get_should_not_be_called,
                             httpcli_post_should_not_be_called);
-  grpc_credentials_get_request_metadata(compute_engine_creds,
+  grpc_credentials_get_request_metadata(compute_engine_creds, test_service_url,
                                         on_oauth2_creds_get_metadata_success,
                                         (void *)test_user_data);
 
@@ -442,7 +449,7 @@ static void test_compute_engine_creds_failure(void) {
                             httpcli_post_should_not_be_called);
   GPR_ASSERT(grpc_credentials_has_request_metadata(compute_engine_creds));
   GPR_ASSERT(grpc_credentials_has_request_metadata_only(compute_engine_creds));
-  grpc_credentials_get_request_metadata(compute_engine_creds,
+  grpc_credentials_get_request_metadata(compute_engine_creds, test_service_url,
                                         on_oauth2_creds_get_metadata_failure,
                                         (void *)test_user_data);
   grpc_credentials_unref(compute_engine_creds);
@@ -468,27 +475,29 @@ static void validate_jwt_encode_and_sign_params(
              !strcmp(json_key->client_email,
                      "777-abaslkan11hlb6nmim3bpspl31ud@developer."
                      "gserviceaccount.com"));
-  GPR_ASSERT(!strcmp(scope, test_scope));
+  if (scope != NULL) GPR_ASSERT(!strcmp(scope, test_scope));
   GPR_ASSERT(!gpr_time_cmp(token_lifetime, grpc_max_auth_token_lifetime));
 }
 
 static char *encode_and_sign_jwt_success(const grpc_auth_json_key *json_key,
-                                         const char *scope,
-                                         gpr_timespec token_lifetime) {
+                                         const char *audience,
+                                         gpr_timespec token_lifetime,
+                                         const char *scope) {
   validate_jwt_encode_and_sign_params(json_key, scope, token_lifetime);
   return gpr_strdup(test_signed_jwt);
 }
 
 static char *encode_and_sign_jwt_failure(const grpc_auth_json_key *json_key,
-                                         const char *scope,
-                                         gpr_timespec token_lifetime) {
+                                         const char *audience,
+                                         gpr_timespec token_lifetime,
+                                         const char *scope) {
   validate_jwt_encode_and_sign_params(json_key, scope, token_lifetime);
   return NULL;
 }
 
 static char *encode_and_sign_jwt_should_not_be_called(
-    const grpc_auth_json_key *json_key, const char *scope,
-    gpr_timespec token_lifetime) {
+    const grpc_auth_json_key *json_key, const char *audience,
+    gpr_timespec token_lifetime, const char *scope) {
   GPR_ASSERT("grpc_jwt_encode_and_sign should not be called" == NULL);
 }
 
@@ -533,7 +542,7 @@ static int service_account_httpcli_post_failure(
   return 1;
 }
 
-static void test_service_accounts_creds_success(void) {
+static void test_service_account_creds_success(void) {
   char *json_key_string = test_json_key_str();
   grpc_credentials *service_account_creds =
       grpc_service_account_credentials_create(json_key_string, test_scope,
@@ -545,7 +554,7 @@ static void test_service_accounts_creds_success(void) {
   grpc_jwt_encode_and_sign_set_override(encode_and_sign_jwt_success);
   grpc_httpcli_set_override(httpcli_get_should_not_be_called,
                             service_account_httpcli_post_success);
-  grpc_credentials_get_request_metadata(service_account_creds,
+  grpc_credentials_get_request_metadata(service_account_creds, test_service_url,
                                         on_oauth2_creds_get_metadata_success,
                                         (void *)test_user_data);
 
@@ -554,7 +563,7 @@ static void test_service_accounts_creds_success(void) {
       encode_and_sign_jwt_should_not_be_called);
   grpc_httpcli_set_override(httpcli_get_should_not_be_called,
                             httpcli_post_should_not_be_called);
-  grpc_credentials_get_request_metadata(service_account_creds,
+  grpc_credentials_get_request_metadata(service_account_creds, test_service_url,
                                         on_oauth2_creds_get_metadata_success,
                                         (void *)test_user_data);
 
@@ -564,7 +573,7 @@ static void test_service_accounts_creds_success(void) {
   grpc_httpcli_set_override(NULL, NULL);
 }
 
-static void test_service_accounts_creds_http_failure(void) {
+static void test_service_account_creds_http_failure(void) {
   char *json_key_string = test_json_key_str();
   grpc_credentials *service_account_creds =
       grpc_service_account_credentials_create(json_key_string, test_scope,
@@ -575,7 +584,7 @@ static void test_service_accounts_creds_http_failure(void) {
   grpc_jwt_encode_and_sign_set_override(encode_and_sign_jwt_success);
   grpc_httpcli_set_override(httpcli_get_should_not_be_called,
                             service_account_httpcli_post_failure);
-  grpc_credentials_get_request_metadata(service_account_creds,
+  grpc_credentials_get_request_metadata(service_account_creds, test_service_url,
                                         on_oauth2_creds_get_metadata_failure,
                                         (void *)test_user_data);
 
@@ -584,7 +593,7 @@ static void test_service_accounts_creds_http_failure(void) {
   grpc_httpcli_set_override(NULL, NULL);
 }
 
-static void test_service_accounts_creds_signing_failure(void) {
+static void test_service_account_creds_signing_failure(void) {
   char *json_key_string = test_json_key_str();
   grpc_credentials *service_account_creds =
       grpc_service_account_credentials_create(json_key_string, test_scope,
@@ -595,13 +604,88 @@ static void test_service_accounts_creds_signing_failure(void) {
   grpc_jwt_encode_and_sign_set_override(encode_and_sign_jwt_failure);
   grpc_httpcli_set_override(httpcli_get_should_not_be_called,
                             httpcli_post_should_not_be_called);
-  grpc_credentials_get_request_metadata(service_account_creds,
+  grpc_credentials_get_request_metadata(service_account_creds, test_service_url,
                                         on_oauth2_creds_get_metadata_failure,
                                         (void *)test_user_data);
 
   gpr_free(json_key_string);
   grpc_credentials_unref(service_account_creds);
   grpc_httpcli_set_override(NULL, NULL);
+  grpc_jwt_encode_and_sign_set_override(NULL);
+}
+
+static void on_jwt_creds_get_metadata_success(
+    void *user_data, grpc_mdelem **md_elems, size_t num_md,
+    grpc_credentials_status status) {
+  char *expected_md_value;
+  gpr_asprintf(&expected_md_value, "Bearer %s", test_signed_jwt);
+  GPR_ASSERT(status == GRPC_CREDENTIALS_OK);
+  GPR_ASSERT(num_md == 1);
+  GPR_ASSERT(
+      !strcmp(grpc_mdstr_as_c_string(md_elems[0]->key), "Authorization"));
+  GPR_ASSERT(
+      !strcmp(grpc_mdstr_as_c_string(md_elems[0]->value), expected_md_value));
+  GPR_ASSERT(user_data != NULL);
+  GPR_ASSERT(!strcmp((const char *)user_data, test_user_data));
+  gpr_free(expected_md_value);
+}
+
+static void on_jwt_creds_get_metadata_failure(
+    void *user_data, grpc_mdelem **md_elems, size_t num_md,
+    grpc_credentials_status status) {
+  GPR_ASSERT(status == GRPC_CREDENTIALS_ERROR);
+  GPR_ASSERT(num_md == 0);
+  GPR_ASSERT(user_data != NULL);
+  GPR_ASSERT(!strcmp((const char *)user_data, test_user_data));
+}
+
+static void test_jwt_creds_success(void) {
+  char *json_key_string = test_json_key_str();
+  grpc_credentials *jwt_creds = grpc_jwt_credentials_create(
+      json_key_string, grpc_max_auth_token_lifetime);
+  GPR_ASSERT(grpc_credentials_has_request_metadata(jwt_creds));
+  GPR_ASSERT(grpc_credentials_has_request_metadata_only(jwt_creds));
+
+  /* First request: jwt_encode_and_sign should be called. */
+  grpc_jwt_encode_and_sign_set_override(encode_and_sign_jwt_success);
+  grpc_credentials_get_request_metadata(jwt_creds, test_service_url,
+                                        on_jwt_creds_get_metadata_success,
+                                        (void *)test_user_data);
+
+  /* Second request: the cached token should be served directly. */
+  grpc_jwt_encode_and_sign_set_override(
+      encode_and_sign_jwt_should_not_be_called);
+  grpc_credentials_get_request_metadata(jwt_creds, test_service_url,
+                                        on_jwt_creds_get_metadata_success,
+                                        (void *)test_user_data);
+
+  /* Third request: Different service url so jwt_encode_and_sign should be
+     called again (no caching). */
+  grpc_jwt_encode_and_sign_set_override(encode_and_sign_jwt_success);
+  grpc_credentials_get_request_metadata(jwt_creds, other_test_service_url,
+                                        on_jwt_creds_get_metadata_success,
+                                        (void *)test_user_data);
+
+  gpr_free(json_key_string);
+  grpc_credentials_unref(jwt_creds);
+  grpc_jwt_encode_and_sign_set_override(NULL);
+}
+
+static void test_jwt_creds_signing_failure(void) {
+  char *json_key_string = test_json_key_str();
+  grpc_credentials *jwt_creds = grpc_jwt_credentials_create(
+      json_key_string, grpc_max_auth_token_lifetime);
+  GPR_ASSERT(grpc_credentials_has_request_metadata(jwt_creds));
+  GPR_ASSERT(grpc_credentials_has_request_metadata_only(jwt_creds));
+
+  grpc_jwt_encode_and_sign_set_override(encode_and_sign_jwt_failure);
+  grpc_credentials_get_request_metadata(jwt_creds, test_service_url,
+                                        on_jwt_creds_get_metadata_failure,
+                                        (void *)test_user_data);
+
+  gpr_free(json_key_string);
+  grpc_credentials_unref(jwt_creds);
+  grpc_jwt_encode_and_sign_set_override(NULL);
 }
 
 int main(int argc, char **argv) {
@@ -618,8 +702,10 @@ int main(int argc, char **argv) {
   test_ssl_oauth2_iam_composite_creds();
   test_compute_engine_creds_success();
   test_compute_engine_creds_failure();
-  test_service_accounts_creds_success();
-  test_service_accounts_creds_http_failure();
-  test_service_accounts_creds_signing_failure();
+  test_service_account_creds_success();
+  test_service_account_creds_http_failure();
+  test_service_account_creds_signing_failure();
+  test_jwt_creds_success();
+  test_jwt_creds_signing_failure();
   return 0;
 }
diff --git a/test/core/security/fetch_oauth2.c b/test/core/security/fetch_oauth2.c
index 6315087448947b0644e7b31e4a99044e14e41941..748a5982fdeb41343874c06b523c54e96147df37 100644
--- a/test/core/security/fetch_oauth2.c
+++ b/test/core/security/fetch_oauth2.c
@@ -139,7 +139,7 @@ int main(int argc, char **argv) {
     }
   } else {
     if (json_key_file_path == NULL) {
-      gpr_log(GPR_ERROR, "missing --json_key option.");
+      gpr_log(GPR_ERROR, "Missing --json_key option.");
       exit(1);
     }
     if (scope == NULL) {
@@ -162,7 +162,7 @@ int main(int argc, char **argv) {
   gpr_cv_init(&sync.cv);
   sync.is_done = 0;
 
-  grpc_credentials_get_request_metadata(creds, on_oauth2_response, &sync);
+  grpc_credentials_get_request_metadata(creds, "", on_oauth2_response, &sync);
 
   gpr_mu_lock(&sync.mu);
   while (!sync.is_done) gpr_cv_wait(&sync.cv, &sync.mu, gpr_inf_future);
diff --git a/test/core/security/json_token_test.c b/test/core/security/json_token_test.c
index 8615fca5fbb4346e2dee1eef096b985215e3d55c..eed0fdf33210cf589060d5d7ce4d3931d0c23314 100644
--- a/test/core/security/json_token_test.c
+++ b/test/core/security/json_token_test.c
@@ -76,6 +76,8 @@ static const char test_json_key_str_part3[] =
 
 static const char test_scope[] = "myperm1 myperm2";
 
+static const char test_service_url[] = "https://foo.com/foo.v1";
+
 static char *test_json_key_str(const char *bad_part3) {
   const char *part3 = bad_part3 != NULL ? bad_part3 : test_json_key_str_part3;
   size_t result_len = strlen(test_json_key_str_part1) +
@@ -229,12 +231,15 @@ static void check_jwt_header(grpc_json *header) {
   grpc_json *ptr;
   grpc_json *alg = NULL;
   grpc_json *typ = NULL;
+  grpc_json *kid = NULL;
 
   for (ptr = header->child; ptr; ptr = ptr->next) {
     if (strcmp(ptr->key, "alg") == 0) {
       alg = ptr;
     } else if (strcmp(ptr->key, "typ") == 0) {
       typ = ptr;
+    } else if (strcmp(ptr->key, "kid") == 0) {
+      kid = ptr;
     }
   }
   GPR_ASSERT(alg != NULL);
@@ -244,9 +249,14 @@ static void check_jwt_header(grpc_json *header) {
   GPR_ASSERT(typ != NULL);
   GPR_ASSERT(typ->type == GRPC_JSON_STRING);
   GPR_ASSERT(!strcmp(typ->value, "JWT"));
+
+  GPR_ASSERT(kid != NULL);
+  GPR_ASSERT(kid->type == GRPC_JSON_STRING);
+  GPR_ASSERT(!strcmp(kid->value, "e6b5137873db8d2ef81e06a47289e6434ec8a165"));
 }
 
-static void check_jwt_claim(grpc_json *claim) {
+static void check_jwt_claim(grpc_json *claim, const char *expected_audience,
+                            const char *expected_scope) {
   gpr_timespec expiration = {0, 0};
   gpr_timespec issue_time = {0, 0};
   gpr_timespec parsed_lifetime;
@@ -255,11 +265,14 @@ static void check_jwt_claim(grpc_json *claim) {
   grpc_json *aud = NULL;
   grpc_json *exp = NULL;
   grpc_json *iat = NULL;
+  grpc_json *sub = NULL;
   grpc_json *ptr;
 
   for (ptr = claim->child; ptr; ptr = ptr->next) {
     if (strcmp(ptr->key, "iss") == 0) {
       iss = ptr;
+    } else if (strcmp(ptr->key, "sub") == 0) {
+      sub = ptr;
     } else if (strcmp(ptr->key, "scope") == 0) {
       scope = ptr;
     } else if (strcmp(ptr->key, "aud") == 0) {
@@ -278,14 +291,22 @@ static void check_jwt_claim(grpc_json *claim) {
           iss->value,
           "777-abaslkan11hlb6nmim3bpspl31ud@developer.gserviceaccount.com"));
 
-  GPR_ASSERT(scope != NULL);
-  GPR_ASSERT(scope->type == GRPC_JSON_STRING);
-  GPR_ASSERT(!strcmp(scope->value, test_scope));
+  if (expected_scope != NULL) {
+    GPR_ASSERT(scope != NULL);
+    GPR_ASSERT(sub == NULL);
+    GPR_ASSERT(scope->type == GRPC_JSON_STRING);
+    GPR_ASSERT(!strcmp(scope->value, expected_scope));
+  } else {
+    /* Claims without scope must have a sub. */
+    GPR_ASSERT(scope == NULL);
+    GPR_ASSERT(sub != NULL);
+    GPR_ASSERT(sub->type == GRPC_JSON_STRING);
+    GPR_ASSERT(!strcmp(iss->value, sub->value));
+  }
 
   GPR_ASSERT(aud != NULL);
   GPR_ASSERT(aud->type == GRPC_JSON_STRING);
-  GPR_ASSERT(!strcmp(aud->value,
-                     "https://www.googleapis.com/oauth2/v3/token"));
+  GPR_ASSERT(!strcmp(aud->value, expected_audience));
 
   GPR_ASSERT(exp != NULL);
   GPR_ASSERT(exp->type == GRPC_JSON_NUMBER);
@@ -324,7 +345,28 @@ static void check_jwt_signature(const char *b64_signature, RSA *rsa_key,
   if (md_ctx != NULL) EVP_MD_CTX_destroy(md_ctx);
 }
 
-static void test_jwt_encode_and_sign(void) {
+static char *service_account_creds_jwt_encode_and_sign(
+    const grpc_auth_json_key *key) {
+  return grpc_jwt_encode_and_sign(key, GRPC_JWT_OAUTH2_AUDIENCE,
+                                  grpc_max_auth_token_lifetime, test_scope);
+}
+
+static char *jwt_creds_jwt_encode_and_sign(const grpc_auth_json_key *key) {
+  return grpc_jwt_encode_and_sign(key, test_service_url,
+                                  grpc_max_auth_token_lifetime, NULL);
+}
+
+static void service_account_creds_check_jwt_claim(grpc_json *claim) {
+  check_jwt_claim(claim, GRPC_JWT_OAUTH2_AUDIENCE, test_scope);
+}
+
+static void jwt_creds_check_jwt_claim(grpc_json *claim) {
+  check_jwt_claim(claim, test_service_url, NULL);
+}
+
+static void test_jwt_encode_and_sign(
+    char *(*jwt_encode_and_sign_func)(const grpc_auth_json_key *),
+    void (*check_jwt_claim_func)(grpc_json *)) {
   char *json_string = test_json_key_str(NULL);
   grpc_json *parsed_header = NULL;
   grpc_json *parsed_claim = NULL;
@@ -333,8 +375,7 @@ static void test_jwt_encode_and_sign(void) {
       grpc_auth_json_key_create_from_string(json_string);
   const char *b64_signature;
   size_t offset = 0;
-  char *jwt = grpc_jwt_encode_and_sign(&json_key, test_scope,
-                                       grpc_max_auth_token_lifetime);
+  char *jwt = jwt_encode_and_sign_func(&json_key);
   const char *dot = strchr(jwt, '.');
   GPR_ASSERT(dot != NULL);
   parsed_header = parse_json_part_from_jwt(jwt, dot - jwt, &scratchpad);
@@ -346,9 +387,10 @@ static void test_jwt_encode_and_sign(void) {
 
   dot = strchr(jwt + offset, '.');
   GPR_ASSERT(dot != NULL);
-  parsed_claim = parse_json_part_from_jwt(jwt + offset, dot - (jwt + offset), &scratchpad);
+  parsed_claim =
+      parse_json_part_from_jwt(jwt + offset, dot - (jwt + offset), &scratchpad);
   GPR_ASSERT(parsed_claim != NULL);
-  check_jwt_claim(parsed_claim);
+  check_jwt_claim_func(parsed_claim);
   offset = dot - jwt + 1;
   grpc_json_destroy(parsed_claim);
   gpr_free(scratchpad);
@@ -363,6 +405,16 @@ static void test_jwt_encode_and_sign(void) {
   gpr_free(jwt);
 }
 
+static void test_service_account_creds_jwt_encode_and_sign(void) {
+  test_jwt_encode_and_sign(service_account_creds_jwt_encode_and_sign,
+                           service_account_creds_check_jwt_claim);
+}
+
+static void test_jwt_creds_jwt_encode_and_sign(void) {
+  test_jwt_encode_and_sign(jwt_creds_jwt_encode_and_sign,
+                           jwt_creds_check_jwt_claim);
+}
+
 int main(int argc, char **argv) {
   grpc_test_init(argc, argv);
   test_parse_json_key_success();
@@ -372,6 +424,7 @@ int main(int argc, char **argv) {
   test_parse_json_key_failure_no_client_email();
   test_parse_json_key_failure_no_private_key_id();
   test_parse_json_key_failure_no_private_key();
-  test_jwt_encode_and_sign();
+  test_service_account_creds_jwt_encode_and_sign();
+  test_jwt_creds_jwt_encode_and_sign();
   return 0;
 }
diff --git a/vsprojects/vs2013/Grpc.mak b/vsprojects/vs2013/Grpc.mak
index a629ef425ec3dd1c3f0d19e87b89240a16b45147..be0c0b8254683ec9235ad11d21a6752282e388fc 100644
--- a/vsprojects/vs2013/Grpc.mak
+++ b/vsprojects/vs2013/Grpc.mak
@@ -434,6 +434,14 @@ grpc_fetch_oauth2: grpc_fetch_oauth2.exe
 	echo Running grpc_fetch_oauth2
 	$(OUT_DIR)\grpc_fetch_oauth2.exe
 
+grpc_create_jwt.exe: grpc_test_util
+	echo Building grpc_create_jwt
+	$(CC) $(CFLAGS) /Fo:$(OUT_DIR)\ ..\..\test\core\security\create_jwt.c 
+	$(LINK) $(LFLAGS) /OUT:"$(OUT_DIR)\grpc_create_jwt.exe" Debug\grpc_test_util.lib Debug\grpc.lib Debug\gpr_test_util.lib Debug\gpr.lib $(LIBS) $(OUT_DIR)\create_jwt.obj 
+grpc_create_jwt: grpc_create_jwt.exe
+	echo Running grpc_create_jwt
+	$(OUT_DIR)\grpc_create_jwt.exe
+
 grpc_json_token_test.exe: grpc_test_util
 	echo Building grpc_json_token_test
 	$(CC) $(CFLAGS) /Fo:$(OUT_DIR)\ ..\..\test\core\security\json_token_test.c