Home | History | Annotate | Download | only in oauth2
      1 /*
      2  *
      3  * Copyright 2015 gRPC authors.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  *
     17  */
     18 
     19 #include <grpc/support/port_platform.h>
     20 
     21 #include "src/core/lib/security/credentials/oauth2/oauth2_credentials.h"
     22 
     23 #include <string.h>
     24 
     25 #include "src/core/lib/security/util/json_util.h"
     26 #include "src/core/lib/surface/api_trace.h"
     27 
     28 #include <grpc/support/alloc.h>
     29 #include <grpc/support/log.h>
     30 #include <grpc/support/string_util.h>
     31 
     32 //
     33 // Auth Refresh Token.
     34 //
     35 
     36 int grpc_auth_refresh_token_is_valid(
     37     const grpc_auth_refresh_token* refresh_token) {
     38   return (refresh_token != nullptr) &&
     39          strcmp(refresh_token->type, GRPC_AUTH_JSON_TYPE_INVALID);
     40 }
     41 
     42 grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json(
     43     const grpc_json* json) {
     44   grpc_auth_refresh_token result;
     45   const char* prop_value;
     46   int success = 0;
     47 
     48   memset(&result, 0, sizeof(grpc_auth_refresh_token));
     49   result.type = GRPC_AUTH_JSON_TYPE_INVALID;
     50   if (json == nullptr) {
     51     gpr_log(GPR_ERROR, "Invalid json.");
     52     goto end;
     53   }
     54 
     55   prop_value = grpc_json_get_string_property(json, "type");
     56   if (prop_value == nullptr ||
     57       strcmp(prop_value, GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER)) {
     58     goto end;
     59   }
     60   result.type = GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER;
     61 
     62   if (!grpc_copy_json_string_property(json, "client_secret",
     63                                       &result.client_secret) ||
     64       !grpc_copy_json_string_property(json, "client_id", &result.client_id) ||
     65       !grpc_copy_json_string_property(json, "refresh_token",
     66                                       &result.refresh_token)) {
     67     goto end;
     68   }
     69   success = 1;
     70 
     71 end:
     72   if (!success) grpc_auth_refresh_token_destruct(&result);
     73   return result;
     74 }
     75 
     76 grpc_auth_refresh_token grpc_auth_refresh_token_create_from_string(
     77     const char* json_string) {
     78   char* scratchpad = gpr_strdup(json_string);
     79   grpc_json* json = grpc_json_parse_string(scratchpad);
     80   grpc_auth_refresh_token result =
     81       grpc_auth_refresh_token_create_from_json(json);
     82   if (json != nullptr) grpc_json_destroy(json);
     83   gpr_free(scratchpad);
     84   return result;
     85 }
     86 
     87 void grpc_auth_refresh_token_destruct(grpc_auth_refresh_token* refresh_token) {
     88   if (refresh_token == nullptr) return;
     89   refresh_token->type = GRPC_AUTH_JSON_TYPE_INVALID;
     90   if (refresh_token->client_id != nullptr) {
     91     gpr_free(refresh_token->client_id);
     92     refresh_token->client_id = nullptr;
     93   }
     94   if (refresh_token->client_secret != nullptr) {
     95     gpr_free(refresh_token->client_secret);
     96     refresh_token->client_secret = nullptr;
     97   }
     98   if (refresh_token->refresh_token != nullptr) {
     99     gpr_free(refresh_token->refresh_token);
    100     refresh_token->refresh_token = nullptr;
    101   }
    102 }
    103 
    104 //
    105 // Oauth2 Token Fetcher credentials.
    106 //
    107 
    108 static void oauth2_token_fetcher_destruct(grpc_call_credentials* creds) {
    109   grpc_oauth2_token_fetcher_credentials* c =
    110       reinterpret_cast<grpc_oauth2_token_fetcher_credentials*>(creds);
    111   GRPC_MDELEM_UNREF(c->access_token_md);
    112   gpr_mu_destroy(&c->mu);
    113   grpc_pollset_set_destroy(grpc_polling_entity_pollset_set(&c->pollent));
    114   grpc_httpcli_context_destroy(&c->httpcli_context);
    115 }
    116 
    117 grpc_credentials_status
    118 grpc_oauth2_token_fetcher_credentials_parse_server_response(
    119     const grpc_http_response* response, grpc_mdelem* token_md,
    120     grpc_millis* token_lifetime) {
    121   char* null_terminated_body = nullptr;
    122   char* new_access_token = nullptr;
    123   grpc_credentials_status status = GRPC_CREDENTIALS_OK;
    124   grpc_json* json = nullptr;
    125 
    126   if (response == nullptr) {
    127     gpr_log(GPR_ERROR, "Received NULL response.");
    128     status = GRPC_CREDENTIALS_ERROR;
    129     goto end;
    130   }
    131 
    132   if (response->body_length > 0) {
    133     null_terminated_body =
    134         static_cast<char*>(gpr_malloc(response->body_length + 1));
    135     null_terminated_body[response->body_length] = '\0';
    136     memcpy(null_terminated_body, response->body, response->body_length);
    137   }
    138 
    139   if (response->status != 200) {
    140     gpr_log(GPR_ERROR, "Call to http server ended with error %d [%s].",
    141             response->status,
    142             null_terminated_body != nullptr ? null_terminated_body : "");
    143     status = GRPC_CREDENTIALS_ERROR;
    144     goto end;
    145   } else {
    146     grpc_json* access_token = nullptr;
    147     grpc_json* token_type = nullptr;
    148     grpc_json* expires_in = nullptr;
    149     grpc_json* ptr;
    150     json = grpc_json_parse_string(null_terminated_body);
    151     if (json == nullptr) {
    152       gpr_log(GPR_ERROR, "Could not parse JSON from %s", null_terminated_body);
    153       status = GRPC_CREDENTIALS_ERROR;
    154       goto end;
    155     }
    156     if (json->type != GRPC_JSON_OBJECT) {
    157       gpr_log(GPR_ERROR, "Response should be a JSON object");
    158       status = GRPC_CREDENTIALS_ERROR;
    159       goto end;
    160     }
    161     for (ptr = json->child; ptr; ptr = ptr->next) {
    162       if (strcmp(ptr->key, "access_token") == 0) {
    163         access_token = ptr;
    164       } else if (strcmp(ptr->key, "token_type") == 0) {
    165         token_type = ptr;
    166       } else if (strcmp(ptr->key, "expires_in") == 0) {
    167         expires_in = ptr;
    168       }
    169     }
    170     if (access_token == nullptr || access_token->type != GRPC_JSON_STRING) {
    171       gpr_log(GPR_ERROR, "Missing or invalid access_token in JSON.");
    172       status = GRPC_CREDENTIALS_ERROR;
    173       goto end;
    174     }
    175     if (token_type == nullptr || token_type->type != GRPC_JSON_STRING) {
    176       gpr_log(GPR_ERROR, "Missing or invalid token_type in JSON.");
    177       status = GRPC_CREDENTIALS_ERROR;
    178       goto end;
    179     }
    180     if (expires_in == nullptr || expires_in->type != GRPC_JSON_NUMBER) {
    181       gpr_log(GPR_ERROR, "Missing or invalid expires_in in JSON.");
    182       status = GRPC_CREDENTIALS_ERROR;
    183       goto end;
    184     }
    185     gpr_asprintf(&new_access_token, "%s %s", token_type->value,
    186                  access_token->value);
    187     *token_lifetime = strtol(expires_in->value, nullptr, 10) * GPR_MS_PER_SEC;
    188     if (!GRPC_MDISNULL(*token_md)) GRPC_MDELEM_UNREF(*token_md);
    189     *token_md = grpc_mdelem_from_slices(
    190         grpc_slice_from_static_string(GRPC_AUTHORIZATION_METADATA_KEY),
    191         grpc_slice_from_copied_string(new_access_token));
    192     status = GRPC_CREDENTIALS_OK;
    193   }
    194 
    195 end:
    196   if (status != GRPC_CREDENTIALS_OK && !GRPC_MDISNULL(*token_md)) {
    197     GRPC_MDELEM_UNREF(*token_md);
    198     *token_md = GRPC_MDNULL;
    199   }
    200   if (null_terminated_body != nullptr) gpr_free(null_terminated_body);
    201   if (new_access_token != nullptr) gpr_free(new_access_token);
    202   if (json != nullptr) grpc_json_destroy(json);
    203   return status;
    204 }
    205 
    206 static void on_oauth2_token_fetcher_http_response(void* user_data,
    207                                                   grpc_error* error) {
    208   GRPC_LOG_IF_ERROR("oauth_fetch", GRPC_ERROR_REF(error));
    209   grpc_credentials_metadata_request* r =
    210       static_cast<grpc_credentials_metadata_request*>(user_data);
    211   grpc_oauth2_token_fetcher_credentials* c =
    212       reinterpret_cast<grpc_oauth2_token_fetcher_credentials*>(r->creds);
    213   grpc_mdelem access_token_md = GRPC_MDNULL;
    214   grpc_millis token_lifetime;
    215   grpc_credentials_status status =
    216       grpc_oauth2_token_fetcher_credentials_parse_server_response(
    217           &r->response, &access_token_md, &token_lifetime);
    218   // Update cache and grab list of pending requests.
    219   gpr_mu_lock(&c->mu);
    220   c->token_fetch_pending = false;
    221   c->access_token_md = GRPC_MDELEM_REF(access_token_md);
    222   c->token_expiration =
    223       status == GRPC_CREDENTIALS_OK
    224           ? gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
    225                          gpr_time_from_millis(token_lifetime, GPR_TIMESPAN))
    226           : gpr_inf_past(GPR_CLOCK_MONOTONIC);
    227   grpc_oauth2_pending_get_request_metadata* pending_request =
    228       c->pending_requests;
    229   c->pending_requests = nullptr;
    230   gpr_mu_unlock(&c->mu);
    231   // Invoke callbacks for all pending requests.
    232   while (pending_request != nullptr) {
    233     if (status == GRPC_CREDENTIALS_OK) {
    234       grpc_credentials_mdelem_array_add(pending_request->md_array,
    235                                         access_token_md);
    236     } else {
    237       error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
    238           "Error occurred when fetching oauth2 token.", &error, 1);
    239     }
    240     GRPC_CLOSURE_SCHED(pending_request->on_request_metadata, error);
    241     grpc_polling_entity_del_from_pollset_set(
    242         pending_request->pollent, grpc_polling_entity_pollset_set(&c->pollent));
    243     grpc_oauth2_pending_get_request_metadata* prev = pending_request;
    244     pending_request = pending_request->next;
    245     gpr_free(prev);
    246   }
    247   GRPC_MDELEM_UNREF(access_token_md);
    248   grpc_call_credentials_unref(r->creds);
    249   grpc_credentials_metadata_request_destroy(r);
    250 }
    251 
    252 static bool oauth2_token_fetcher_get_request_metadata(
    253     grpc_call_credentials* creds, grpc_polling_entity* pollent,
    254     grpc_auth_metadata_context context, grpc_credentials_mdelem_array* md_array,
    255     grpc_closure* on_request_metadata, grpc_error** error) {
    256   grpc_oauth2_token_fetcher_credentials* c =
    257       reinterpret_cast<grpc_oauth2_token_fetcher_credentials*>(creds);
    258   // Check if we can use the cached token.
    259   grpc_millis refresh_threshold =
    260       GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS * GPR_MS_PER_SEC;
    261   grpc_mdelem cached_access_token_md = GRPC_MDNULL;
    262   gpr_mu_lock(&c->mu);
    263   if (!GRPC_MDISNULL(c->access_token_md) &&
    264       gpr_time_cmp(
    265           gpr_time_sub(c->token_expiration, gpr_now(GPR_CLOCK_MONOTONIC)),
    266           gpr_time_from_seconds(GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS,
    267                                 GPR_TIMESPAN)) > 0) {
    268     cached_access_token_md = GRPC_MDELEM_REF(c->access_token_md);
    269   }
    270   if (!GRPC_MDISNULL(cached_access_token_md)) {
    271     gpr_mu_unlock(&c->mu);
    272     grpc_credentials_mdelem_array_add(md_array, cached_access_token_md);
    273     GRPC_MDELEM_UNREF(cached_access_token_md);
    274     return true;
    275   }
    276   // Couldn't get the token from the cache.
    277   // Add request to c->pending_requests and start a new fetch if needed.
    278   grpc_oauth2_pending_get_request_metadata* pending_request =
    279       static_cast<grpc_oauth2_pending_get_request_metadata*>(
    280           gpr_malloc(sizeof(*pending_request)));
    281   pending_request->md_array = md_array;
    282   pending_request->on_request_metadata = on_request_metadata;
    283   pending_request->pollent = pollent;
    284   grpc_polling_entity_add_to_pollset_set(
    285       pollent, grpc_polling_entity_pollset_set(&c->pollent));
    286   pending_request->next = c->pending_requests;
    287   c->pending_requests = pending_request;
    288   bool start_fetch = false;
    289   if (!c->token_fetch_pending) {
    290     c->token_fetch_pending = true;
    291     start_fetch = true;
    292   }
    293   gpr_mu_unlock(&c->mu);
    294   if (start_fetch) {
    295     grpc_call_credentials_ref(creds);
    296     c->fetch_func(grpc_credentials_metadata_request_create(creds),
    297                   &c->httpcli_context, &c->pollent,
    298                   on_oauth2_token_fetcher_http_response,
    299                   grpc_core::ExecCtx::Get()->Now() + refresh_threshold);
    300   }
    301   return false;
    302 }
    303 
    304 static void oauth2_token_fetcher_cancel_get_request_metadata(
    305     grpc_call_credentials* creds, grpc_credentials_mdelem_array* md_array,
    306     grpc_error* error) {
    307   grpc_oauth2_token_fetcher_credentials* c =
    308       reinterpret_cast<grpc_oauth2_token_fetcher_credentials*>(creds);
    309   gpr_mu_lock(&c->mu);
    310   grpc_oauth2_pending_get_request_metadata* prev = nullptr;
    311   grpc_oauth2_pending_get_request_metadata* pending_request =
    312       c->pending_requests;
    313   while (pending_request != nullptr) {
    314     if (pending_request->md_array == md_array) {
    315       // Remove matching pending request from the list.
    316       if (prev != nullptr) {
    317         prev->next = pending_request->next;
    318       } else {
    319         c->pending_requests = pending_request->next;
    320       }
    321       // Invoke the callback immediately with an error.
    322       GRPC_CLOSURE_SCHED(pending_request->on_request_metadata,
    323                          GRPC_ERROR_REF(error));
    324       gpr_free(pending_request);
    325       break;
    326     }
    327     prev = pending_request;
    328     pending_request = pending_request->next;
    329   }
    330   gpr_mu_unlock(&c->mu);
    331   GRPC_ERROR_UNREF(error);
    332 }
    333 
    334 static void init_oauth2_token_fetcher(grpc_oauth2_token_fetcher_credentials* c,
    335                                       grpc_fetch_oauth2_func fetch_func) {
    336   memset(c, 0, sizeof(grpc_oauth2_token_fetcher_credentials));
    337   c->base.type = GRPC_CALL_CREDENTIALS_TYPE_OAUTH2;
    338   gpr_ref_init(&c->base.refcount, 1);
    339   gpr_mu_init(&c->mu);
    340   c->token_expiration = gpr_inf_past(GPR_CLOCK_MONOTONIC);
    341   c->fetch_func = fetch_func;
    342   c->pollent =
    343       grpc_polling_entity_create_from_pollset_set(grpc_pollset_set_create());
    344   grpc_httpcli_context_init(&c->httpcli_context);
    345 }
    346 
    347 //
    348 //  Google Compute Engine credentials.
    349 //
    350 
    351 static grpc_call_credentials_vtable compute_engine_vtable = {
    352     oauth2_token_fetcher_destruct, oauth2_token_fetcher_get_request_metadata,
    353     oauth2_token_fetcher_cancel_get_request_metadata};
    354 
    355 static void compute_engine_fetch_oauth2(
    356     grpc_credentials_metadata_request* metadata_req,
    357     grpc_httpcli_context* httpcli_context, grpc_polling_entity* pollent,
    358     grpc_iomgr_cb_func response_cb, grpc_millis deadline) {
    359   grpc_http_header header = {(char*)"Metadata-Flavor", (char*)"Google"};
    360   grpc_httpcli_request request;
    361   memset(&request, 0, sizeof(grpc_httpcli_request));
    362   request.host = (char*)GRPC_COMPUTE_ENGINE_METADATA_HOST;
    363   request.http.path = (char*)GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH;
    364   request.http.hdr_count = 1;
    365   request.http.hdrs = &header;
    366   /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host
    367      channel. This would allow us to cancel an authentication query when under
    368      extreme memory pressure. */
    369   grpc_resource_quota* resource_quota =
    370       grpc_resource_quota_create("oauth2_credentials");
    371   grpc_httpcli_get(
    372       httpcli_context, pollent, resource_quota, &request, deadline,
    373       GRPC_CLOSURE_CREATE(response_cb, metadata_req, grpc_schedule_on_exec_ctx),
    374       &metadata_req->response);
    375   grpc_resource_quota_unref_internal(resource_quota);
    376 }
    377 
    378 grpc_call_credentials* grpc_google_compute_engine_credentials_create(
    379     void* reserved) {
    380   grpc_oauth2_token_fetcher_credentials* c =
    381       static_cast<grpc_oauth2_token_fetcher_credentials*>(
    382           gpr_malloc(sizeof(grpc_oauth2_token_fetcher_credentials)));
    383   GRPC_API_TRACE("grpc_compute_engine_credentials_create(reserved=%p)", 1,
    384                  (reserved));
    385   GPR_ASSERT(reserved == nullptr);
    386   init_oauth2_token_fetcher(c, compute_engine_fetch_oauth2);
    387   c->base.vtable = &compute_engine_vtable;
    388   return &c->base;
    389 }
    390 
    391 //
    392 // Google Refresh Token credentials.
    393 //
    394 
    395 static void refresh_token_destruct(grpc_call_credentials* creds) {
    396   grpc_google_refresh_token_credentials* c =
    397       reinterpret_cast<grpc_google_refresh_token_credentials*>(creds);
    398   grpc_auth_refresh_token_destruct(&c->refresh_token);
    399   oauth2_token_fetcher_destruct(&c->base.base);
    400 }
    401 
    402 static grpc_call_credentials_vtable refresh_token_vtable = {
    403     refresh_token_destruct, oauth2_token_fetcher_get_request_metadata,
    404     oauth2_token_fetcher_cancel_get_request_metadata};
    405 
    406 static void refresh_token_fetch_oauth2(
    407     grpc_credentials_metadata_request* metadata_req,
    408     grpc_httpcli_context* httpcli_context, grpc_polling_entity* pollent,
    409     grpc_iomgr_cb_func response_cb, grpc_millis deadline) {
    410   grpc_google_refresh_token_credentials* c =
    411       reinterpret_cast<grpc_google_refresh_token_credentials*>(
    412           metadata_req->creds);
    413   grpc_http_header header = {(char*)"Content-Type",
    414                              (char*)"application/x-www-form-urlencoded"};
    415   grpc_httpcli_request request;
    416   char* body = nullptr;
    417   gpr_asprintf(&body, GRPC_REFRESH_TOKEN_POST_BODY_FORMAT_STRING,
    418                c->refresh_token.client_id, c->refresh_token.client_secret,
    419                c->refresh_token.refresh_token);
    420   memset(&request, 0, sizeof(grpc_httpcli_request));
    421   request.host = (char*)GRPC_GOOGLE_OAUTH2_SERVICE_HOST;
    422   request.http.path = (char*)GRPC_GOOGLE_OAUTH2_SERVICE_TOKEN_PATH;
    423   request.http.hdr_count = 1;
    424   request.http.hdrs = &header;
    425   request.handshaker = &grpc_httpcli_ssl;
    426   /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host
    427      channel. This would allow us to cancel an authentication query when under
    428      extreme memory pressure. */
    429   grpc_resource_quota* resource_quota =
    430       grpc_resource_quota_create("oauth2_credentials_refresh");
    431   grpc_httpcli_post(
    432       httpcli_context, pollent, resource_quota, &request, body, strlen(body),
    433       deadline,
    434       GRPC_CLOSURE_CREATE(response_cb, metadata_req, grpc_schedule_on_exec_ctx),
    435       &metadata_req->response);
    436   grpc_resource_quota_unref_internal(resource_quota);
    437   gpr_free(body);
    438 }
    439 
    440 grpc_call_credentials*
    441 grpc_refresh_token_credentials_create_from_auth_refresh_token(
    442     grpc_auth_refresh_token refresh_token) {
    443   grpc_google_refresh_token_credentials* c;
    444   if (!grpc_auth_refresh_token_is_valid(&refresh_token)) {
    445     gpr_log(GPR_ERROR, "Invalid input for refresh token credentials creation");
    446     return nullptr;
    447   }
    448   c = static_cast<grpc_google_refresh_token_credentials*>(
    449       gpr_zalloc(sizeof(grpc_google_refresh_token_credentials)));
    450   init_oauth2_token_fetcher(&c->base, refresh_token_fetch_oauth2);
    451   c->base.base.vtable = &refresh_token_vtable;
    452   c->refresh_token = refresh_token;
    453   return &c->base.base;
    454 }
    455 
    456 static char* create_loggable_refresh_token(grpc_auth_refresh_token* token) {
    457   if (strcmp(token->type, GRPC_AUTH_JSON_TYPE_INVALID) == 0) {
    458     return gpr_strdup("<Invalid json token>");
    459   }
    460   char* loggable_token = nullptr;
    461   gpr_asprintf(&loggable_token,
    462                "{\n type: %s\n client_id: %s\n client_secret: "
    463                "<redacted>\n refresh_token: <redacted>\n}",
    464                token->type, token->client_id);
    465   return loggable_token;
    466 }
    467 
    468 grpc_call_credentials* grpc_google_refresh_token_credentials_create(
    469     const char* json_refresh_token, void* reserved) {
    470   grpc_auth_refresh_token token =
    471       grpc_auth_refresh_token_create_from_string(json_refresh_token);
    472   if (grpc_api_trace.enabled()) {
    473     char* loggable_token = create_loggable_refresh_token(&token);
    474     gpr_log(GPR_INFO,
    475             "grpc_refresh_token_credentials_create(json_refresh_token=%s, "
    476             "reserved=%p)",
    477             loggable_token, reserved);
    478     gpr_free(loggable_token);
    479   }
    480   GPR_ASSERT(reserved == nullptr);
    481   return grpc_refresh_token_credentials_create_from_auth_refresh_token(token);
    482 }
    483 
    484 //
    485 // Oauth2 Access Token credentials.
    486 //
    487 
    488 static void access_token_destruct(grpc_call_credentials* creds) {
    489   grpc_access_token_credentials* c =
    490       reinterpret_cast<grpc_access_token_credentials*>(creds);
    491   GRPC_MDELEM_UNREF(c->access_token_md);
    492 }
    493 
    494 static bool access_token_get_request_metadata(
    495     grpc_call_credentials* creds, grpc_polling_entity* pollent,
    496     grpc_auth_metadata_context context, grpc_credentials_mdelem_array* md_array,
    497     grpc_closure* on_request_metadata, grpc_error** error) {
    498   grpc_access_token_credentials* c =
    499       reinterpret_cast<grpc_access_token_credentials*>(creds);
    500   grpc_credentials_mdelem_array_add(md_array, c->access_token_md);
    501   return true;
    502 }
    503 
    504 static void access_token_cancel_get_request_metadata(
    505     grpc_call_credentials* c, grpc_credentials_mdelem_array* md_array,
    506     grpc_error* error) {
    507   GRPC_ERROR_UNREF(error);
    508 }
    509 
    510 static grpc_call_credentials_vtable access_token_vtable = {
    511     access_token_destruct, access_token_get_request_metadata,
    512     access_token_cancel_get_request_metadata};
    513 
    514 grpc_call_credentials* grpc_access_token_credentials_create(
    515     const char* access_token, void* reserved) {
    516   grpc_access_token_credentials* c =
    517       static_cast<grpc_access_token_credentials*>(
    518           gpr_zalloc(sizeof(grpc_access_token_credentials)));
    519   GRPC_API_TRACE(
    520       "grpc_access_token_credentials_create(access_token=<redacted>, "
    521       "reserved=%p)",
    522       1, (reserved));
    523   GPR_ASSERT(reserved == nullptr);
    524   c->base.type = GRPC_CALL_CREDENTIALS_TYPE_OAUTH2;
    525   c->base.vtable = &access_token_vtable;
    526   gpr_ref_init(&c->base.refcount, 1);
    527   char* token_md_value;
    528   gpr_asprintf(&token_md_value, "Bearer %s", access_token);
    529   grpc_core::ExecCtx exec_ctx;
    530   c->access_token_md = grpc_mdelem_from_slices(
    531       grpc_slice_from_static_string(GRPC_AUTHORIZATION_METADATA_KEY),
    532       grpc_slice_from_copied_string(token_md_value));
    533 
    534   gpr_free(token_md_value);
    535   return &c->base;
    536 }
    537