[identity] Copy google_auth_provider from Topaz

This is step 1 of 4 in a soft transition to migrate the Auth Provider
between repos. It creates a new component but the component is not yet
included in any configurations or used.

Change-Id: I3b115347b16129b0be72d7ff1c59310d4105573d
diff --git a/src/identity/BUILD.gn b/src/identity/BUILD.gn
index 37ffb68..74b1828 100644
--- a/src/identity/BUILD.gn
+++ b/src/identity/BUILD.gn
@@ -6,6 +6,7 @@
   testonly = true
   deps = [
     ":tests",
+    "bin",
     "lib",
   ]
 }
@@ -14,6 +15,7 @@
   testonly = true
 
   data_deps = [
+    "bin:tests",
     "lib:tests",
   ]
 }
diff --git a/src/identity/bin/BUILD.gn b/src/identity/bin/BUILD.gn
new file mode 100644
index 0000000..2ca63fc0
--- /dev/null
+++ b/src/identity/bin/BUILD.gn
@@ -0,0 +1,33 @@
+# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/test/test_package.gni")
+import("//build/testing/environments.gni")
+
+group("bin") {
+  testonly = true
+  deps = [
+    ":identity_bin_unittests",
+  ]
+}
+
+group("tests") {
+  testonly = true
+  data_deps = [
+    ":identity_bin_unittests",
+  ]
+}
+
+test_package("identity_bin_unittests") {
+  deps = [
+    "google_auth_provider:google_auth_provider_unittests",
+  ]
+
+  tests = [
+    {
+      name = "google_auth_provider_unittests"
+      environments = basic_envs
+    },
+  ]
+}
diff --git a/src/identity/bin/google_auth_provider/BUILD.gn b/src/identity/bin/google_auth_provider/BUILD.gn
new file mode 100644
index 0000000..0f226bb
--- /dev/null
+++ b/src/identity/bin/google_auth_provider/BUILD.gn
@@ -0,0 +1,108 @@
+# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/package.gni")
+
+# TODO(jsankey): Complete soft transition to migrate users away from
+# the conflicting target in Topaz then remove the _2.
+package("google_auth_provider_2") {
+  deps = [
+    ":bin",
+  ]
+
+  binary = "google_auth_provider_2"
+
+  meta = [
+    {
+      path = rebase_path("meta/google_auth_provider_2.cmx")
+      dest = "google_auth_provider_2.cmx"
+    },
+  ]
+}
+
+executable("bin") {
+  output_name = "google_auth_provider_2"
+
+  sources = [
+    "main.cc",
+  ]
+
+  deps = [
+    ":lib",
+    "//garnet/public/lib/fsl",
+    "//garnet/public/lib/network_wrapper",
+    "//sdk/fidl/fuchsia.auth",
+    "//sdk/lib/fidl/cpp",
+    "//sdk/lib/sys/cpp",
+    "//src/lib/fxl",
+    "//zircon/public/lib/async-loop-cpp",
+    "//zircon/public/lib/fit",
+    "//zircon/public/lib/trace-provider",
+  ]
+}
+
+executable("google_auth_provider_unittests") {
+  testonly = true
+
+  deps = [
+    ":unittests",
+    "//third_party/googletest:gtest_main",
+  ]
+}
+
+source_set("unittests") {
+  testonly = true
+  output_name = "google_auth_provider_unittests"
+
+  sources = [
+    "factory_impl_unittest.cc",
+    "google_auth_provider_impl_unittest.cc",
+  ]
+
+  deps = [
+    ":lib",
+    "//garnet/public/lib/gtest:gtest",
+    "//garnet/public/lib/network_wrapper:fake",
+    "//peridot/lib/rapidjson",
+    "//sdk/fidl/fuchsia.auth",
+    "//sdk/lib/fidl/cpp",
+    "//src/lib/fxl:printers",
+    "//third_party/googletest:gtest_main",
+  ]
+}
+
+source_set("lib") {
+  sources = [
+    "constants.h",
+    "factory_impl.cc",
+    "factory_impl.h",
+    "google_auth_provider_impl.cc",
+    "google_auth_provider_impl.h",
+    "settings.h",
+  ]
+
+  deps = [
+    "//garnet/public/lib/rapidjson_utils",
+    "//peridot/lib/rapidjson",
+    "//sdk/lib/ui/scenic/cpp",
+    "//src/identity/lib/oauth:lib",
+    "//src/lib/fxl",
+  ]
+
+  public_deps = [
+    "//garnet/public/lib/callback",
+    "//garnet/public/lib/network_wrapper",
+    "//sdk/fidl/fuchsia.auth",
+    "//sdk/fidl/fuchsia.auth.testing",
+    "//sdk/fidl/fuchsia.net.oldhttp",
+    "//sdk/fidl/fuchsia.ui.views",
+    "//sdk/fidl/fuchsia.web",
+    "//sdk/lib/fidl/cpp",
+    "//sdk/lib/sys/cpp",
+    "//zircon/public/lib/async-default",
+    "//zircon/public/lib/fit",
+    "//zircon/public/lib/syslog",
+    "//zircon/public/lib/zx",
+  ]
+}
diff --git a/src/identity/bin/google_auth_provider/constants.h b/src/identity/bin/google_auth_provider/constants.h
new file mode 100644
index 0000000..7072d27
--- /dev/null
+++ b/src/identity/bin/google_auth_provider/constants.h
@@ -0,0 +1,41 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <initializer_list>
+
+namespace google_auth_provider {
+
+constexpr char kFuchsiaClientId[] =
+    "934259141868-rejmm4ollj1bs7th1vg2ur6antpbug79.apps.googleusercontent.com";
+constexpr char kGoogleOAuthAuthEndpoint[] =
+    "https://accounts.google.com/o/oauth2/v2/auth";
+constexpr char kGoogleFuchsiaEndpoint[] =
+    "https://accounts.google.com/embedded/setup/fuchsia";
+constexpr char kGoogleOAuthTokenEndpoint[] =
+    "https://www.googleapis.com/oauth2/v4/token";
+constexpr char kGoogleRevokeTokenEndpoint[] =
+    "https://accounts.google.com/o/oauth2/revoke";
+constexpr char kGoogleUserInfoEndpoint[] =
+    "https://www.googleapis.com/oauth2/v3/userinfo";
+constexpr char kFirebaseAuthEndpoint[] =
+    "https://www.googleapis.com/identitytoolkit/v3/relyingparty/"
+    "verifyAssertion";
+constexpr char kRedirectUri[] = "https://localhost/fuchsiaoauth2redirect";
+constexpr char kWebViewUrl[] = "web_view";
+
+constexpr auto kScopes = {
+    // Used by google_auth_provider for retrieving unique user profile id.
+    "openid",
+    // Used by a variety of client components.
+    "email",
+    // Used by google_auth_provider for retrieving user profile attributes,
+    // specifically display name, profile url, and profile image.
+    "profile",
+    // Used by components outside this repository.
+    "https://www.googleapis.com/auth/assistant",
+    // Used by components outside this repository.
+    "https://www.googleapis.com/auth/youtube.readonly",
+    // Used by software update
+    "https://www.googleapis.com/auth/devstorage.read_write"};
+}  // namespace google_auth_provider
diff --git a/src/identity/bin/google_auth_provider/factory_impl.cc b/src/identity/bin/google_auth_provider/factory_impl.cc
new file mode 100644
index 0000000..83907a6
--- /dev/null
+++ b/src/identity/bin/google_auth_provider/factory_impl.cc
@@ -0,0 +1,38 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/identity/bin/google_auth_provider/factory_impl.h"
+
+#include "src/identity/bin/google_auth_provider/settings.h"
+
+namespace google_auth_provider {
+
+FactoryImpl::FactoryImpl(async_dispatcher_t* main_dispatcher,
+                         sys::ComponentContext* context,
+                         network_wrapper::NetworkWrapper* network_wrapper,
+                         Settings settings)
+    : main_dispatcher_(main_dispatcher),
+      context_(context),
+      network_wrapper_(network_wrapper),
+      settings_(std::move(settings)) {
+  FXL_DCHECK(context_);
+  FXL_DCHECK(network_wrapper_);
+}
+
+FactoryImpl::~FactoryImpl() {}
+
+void FactoryImpl::Bind(
+    fidl::InterfaceRequest<fuchsia::auth::AuthProviderFactory> request) {
+  factory_bindings_.AddBinding(this, std::move(request));
+}
+
+void FactoryImpl::GetAuthProvider(
+    fidl::InterfaceRequest<fuchsia::auth::AuthProvider> auth_provider,
+    GetAuthProviderCallback callback) {
+  providers_.emplace(main_dispatcher_, context_, network_wrapper_, settings_,
+                     std::move(auth_provider));
+  callback(fuchsia::auth::AuthProviderStatus::OK);
+}
+
+}  // namespace google_auth_provider
diff --git a/src/identity/bin/google_auth_provider/factory_impl.h b/src/identity/bin/google_auth_provider/factory_impl.h
new file mode 100644
index 0000000..c3d335d
--- /dev/null
+++ b/src/identity/bin/google_auth_provider/factory_impl.h
@@ -0,0 +1,50 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_IDENTITY_BIN_GOOGLE_AUTH_PROVIDER_FACTORY_IMPL_H_
+#define SRC_IDENTITY_BIN_GOOGLE_AUTH_PROVIDER_FACTORY_IMPL_H_
+
+#include <fuchsia/auth/cpp/fidl.h>
+#include <lib/async/dispatcher.h>
+
+#include "lib/callback/auto_cleanable.h"
+#include "lib/network_wrapper/network_wrapper.h"
+#include "src/identity/bin/google_auth_provider/google_auth_provider_impl.h"
+#include "src/identity/bin/google_auth_provider/settings.h"
+#include "src/lib/fxl/macros.h"
+
+namespace google_auth_provider {
+
+class FactoryImpl : public fuchsia::auth::AuthProviderFactory {
+ public:
+  FactoryImpl(async_dispatcher_t* main_dispatcher,
+              sys::ComponentContext* context,
+              network_wrapper::NetworkWrapper* network_wrapper,
+              Settings settings);
+
+  ~FactoryImpl() override;
+
+  void Bind(fidl::InterfaceRequest<fuchsia::auth::AuthProviderFactory> request);
+
+ private:
+  // Factory:
+  void GetAuthProvider(
+      fidl::InterfaceRequest<fuchsia::auth::AuthProvider> auth_provider,
+      GetAuthProviderCallback callback) override;
+
+  async_dispatcher_t* const main_dispatcher_;
+  sys::ComponentContext* const context_;
+  network_wrapper::NetworkWrapper* const network_wrapper_;
+  const Settings settings_;
+
+  callback::AutoCleanableSet<GoogleAuthProviderImpl> providers_;
+
+  fidl::BindingSet<fuchsia::auth::AuthProviderFactory> factory_bindings_;
+
+  FXL_DISALLOW_COPY_AND_ASSIGN(FactoryImpl);
+};
+
+}  // namespace google_auth_provider
+
+#endif  // SRC_IDENTITY_BIN_GOOGLE_AUTH_PROVIDER_FACTORY_IMPL_H_
diff --git a/src/identity/bin/google_auth_provider/factory_impl_unittest.cc b/src/identity/bin/google_auth_provider/factory_impl_unittest.cc
new file mode 100644
index 0000000..5748051
--- /dev/null
+++ b/src/identity/bin/google_auth_provider/factory_impl_unittest.cc
@@ -0,0 +1,52 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/identity/bin/google_auth_provider/factory_impl.h"
+
+#include "lib/callback/capture.h"
+#include "lib/callback/set_when_called.h"
+#include "lib/fidl/cpp/binding.h"
+#include "lib/gtest/test_loop_fixture.h"
+#include "lib/network_wrapper/fake_network_wrapper.h"
+#include "src/identity/bin/google_auth_provider/settings.h"
+#include "src/lib/fxl/macros.h"
+
+namespace google_auth_provider {
+
+class GoogleFactoryImplTest : public gtest::TestLoopFixture {
+ public:
+  GoogleFactoryImplTest()
+      : network_wrapper_(dispatcher()),
+        context_(sys::ComponentContext::Create().get()),
+        factory_impl_(dispatcher(), context_, &network_wrapper_, {}) {
+    factory_impl_.Bind(factory_.NewRequest());
+  }
+
+  ~GoogleFactoryImplTest() override {}
+
+ protected:
+  network_wrapper::FakeNetworkWrapper network_wrapper_;
+  sys::ComponentContext* context_;
+  fuchsia::auth::AuthProviderPtr auth_provider_;
+  fuchsia::auth::AuthProviderFactoryPtr factory_;
+
+  FactoryImpl factory_impl_;
+
+ private:
+  FXL_DISALLOW_COPY_AND_ASSIGN(GoogleFactoryImplTest);
+};
+
+TEST_F(GoogleFactoryImplTest, GetAuthProvider) {
+  fuchsia::auth::AuthProviderStatus status;
+  auth_provider_.Unbind();
+  bool callback_called = false;
+  factory_->GetAuthProvider(
+      auth_provider_.NewRequest(),
+      callback::Capture(callback::SetWhenCalled(&callback_called), &status));
+  RunLoopUntilIdle();
+  EXPECT_TRUE(callback_called);
+  EXPECT_EQ(fuchsia::auth::AuthProviderStatus::OK, status);
+}
+
+}  // namespace google_auth_provider
diff --git a/src/identity/bin/google_auth_provider/google_auth_provider_impl.cc b/src/identity/bin/google_auth_provider/google_auth_provider_impl.cc
new file mode 100644
index 0000000..2a96cfb
--- /dev/null
+++ b/src/identity/bin/google_auth_provider/google_auth_provider_impl.cc
@@ -0,0 +1,683 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/identity/bin/google_auth_provider/google_auth_provider_impl.h"
+
+#include <fuchsia/net/oldhttp/cpp/fidl.h>
+#include <lib/async/dispatcher.h>
+#include <lib/fdio/directory.h>
+#include <lib/fidl/cpp/interface_request.h>
+#include <lib/fit/function.h>
+#include <lib/sys/cpp/component_context.h>
+#include <lib/syslog/global.h>
+#include <lib/ui/scenic/cpp/view_token_pair.h>
+#include <lib/vfs/cpp/service.h>
+
+#include "garnet/public/lib/rapidjson_utils/rapidjson_validation.h"
+#include "peridot/lib/rapidjson/rapidjson.h"
+#include "rapidjson/document.h"
+#include "rapidjson/schema.h"
+#include "src/identity/bin/google_auth_provider/constants.h"
+#include "src/identity/lib/oauth/oauth_request_builder.h"
+#include "src/identity/lib/oauth/oauth_response.h"
+#include "src/lib/fxl/strings/join_strings.h"
+
+namespace google_auth_provider {
+
+namespace http = ::fuchsia::net::oldhttp;
+
+namespace {
+
+constexpr char kInjectionEntry[] = "LegacyAuthCredentialInjector";
+
+std::string GetClientId(const std::string& app_client_id) {
+  // By default, use the client_id of the invoking application.
+  std::string client_id = app_client_id;
+
+  // Use hard-coded Fuchsia client_id for downscoped tokens, if |app_client_id|
+  // is missing.
+  if (app_client_id.empty()) {
+    client_id = kFuchsiaClientId;
+  }
+
+  return client_id;
+}
+
+// Outputs information from a failing OAuthResponse to the syslog.
+void LogOauthResponse(const char* operation,
+                      const auth_providers::oauth::OAuthResponse& response) {
+  FX_LOGF(WARNING, NULL,
+          "OAuthResponse error during %s: %s (Full response: %s)", operation,
+          response.error_description.c_str(),
+          modular::JsonValueToPrettyString(response.json_response).c_str());
+}
+
+// Sometimes auth codes contain non alpha characters such as a slash. When we
+// receive these in a url parameter they are Hex encoded, but they need to be
+// translated back to UTF-8 before using the auth code.
+//
+// TODO(jsankey): Remove this once we migrate to cookie delivery, or use a
+// common encoding/decoding library if that arrives earlier.
+void NormalizeAuthCode(std::string* code) {
+  // This function uses the following literals:
+  //   1 - The length of '%'
+  //   2 - The length of a hex byte, e.g. '2F'
+  //   3 - The length of a %-prefixed hex byte, e.g. '%2F'
+  //  16 - The base of hexadecimal
+  //  32 - The smallest printable character, i.e. the space character
+  // 127 - The largest single byte UTF-8 codepoint
+  std::string::size_type pos = 0;
+  while ((pos = code->find("%", pos)) != std::string::npos &&
+         pos <= code->length() - 3) {
+    int codepoint = strtol(code->substr(pos + 1, 2).c_str(), nullptr, 16);
+    if (codepoint >= 33 && codepoint <= 127) {
+      code->replace(pos, 3, std::string(1, codepoint));
+    }
+    pos += 3;
+  }
+}
+
+// Checks the supplied Google authentication URL. If the URL indicated the user
+// has aborted the flow or an error occured these are reported as error
+// statuses, otherwise a status of OK is returned. If the URL contains an auth
+// code query parameter, this will be returned in |auth_code|.
+fuchsia::auth::AuthProviderStatus ParseAuthCodeFromUrl(const std::string& url,
+                                                       std::string& auth_code) {
+  static const std::string success_prefix =
+      std::string{kRedirectUri} + "?code=";
+  static const std::string cancel_prefix =
+      std::string{kRedirectUri} + "?error=access_denied";
+
+  if (url.find(cancel_prefix) == 0) {
+    return fuchsia::auth::AuthProviderStatus::USER_CANCELLED;
+  }
+  if (url.find(success_prefix) != 0) {
+    // The authentication process is still ongoing.
+    return fuchsia::auth::AuthProviderStatus::OK;
+  }
+
+  // Take everything up to the next query parameter or hash fragment.
+  auto end_char = url.find_first_of("#&", success_prefix.size());
+  auto length = end_char == std::string::npos
+                    ? std::string::npos
+                    : end_char - success_prefix.size();
+  auto code = url.substr(success_prefix.size(), length);
+  NormalizeAuthCode(&code);
+
+  if (code.empty()) {
+    return fuchsia::auth::AuthProviderStatus::OAUTH_SERVER_ERROR;
+  } else {
+    auth_code = code;
+    return fuchsia::auth::AuthProviderStatus::OK;
+  }
+}
+
+}  // namespace
+
+using auth_providers::oauth::OAuthRequestBuilder;
+using auth_providers::oauth::ParseOAuthResponse;
+using fuchsia::auth::AuthenticationUIContext;
+using fuchsia::auth::AuthProviderStatus;
+using fuchsia::auth::AuthTokenPtr;
+using fuchsia::auth::FirebaseTokenPtr;
+using modular::JsonValueToPrettyString;
+
+GoogleAuthProviderImpl::GoogleAuthProviderImpl(
+    async_dispatcher_t* const main_dispatcher, sys::ComponentContext* context,
+    network_wrapper::NetworkWrapper* network_wrapper, Settings settings,
+    fidl::InterfaceRequest<fuchsia::auth::AuthProvider> request)
+    : main_dispatcher_(main_dispatcher),
+      context_(context),
+      network_wrapper_(network_wrapper),
+      settings_(std::move(settings)),
+      binding_(this, std::move(request)) {
+  FXL_DCHECK(main_dispatcher_);
+  FXL_DCHECK(network_wrapper_);
+
+  // The class shuts down when the client connection is disconnected.
+  binding_.set_error_handler([this](zx_status_t status) {
+    if (on_empty_) {
+      on_empty_();
+    }
+  });
+}
+
+GoogleAuthProviderImpl::~GoogleAuthProviderImpl() {}
+
+void GoogleAuthProviderImpl::GetPersistentCredential(
+    fidl::InterfaceHandle<AuthenticationUIContext> auth_ui_context,
+    fidl::StringPtr user_profile_id, GetPersistentCredentialCallback callback) {
+  FXL_DCHECK(auth_ui_context);
+  get_persistent_credential_callback_ = std::move(callback);
+
+  std::string url = GetAuthorizeUrl(user_profile_id);
+  fuchsia::ui::views::ViewHolderToken view_holder_token = SetupChromium();
+  if (!view_holder_token.value) {
+    return;
+  }
+  fuchsia::web::NavigationControllerPtr controller;
+  web_frame_->GetNavigationController(controller.NewRequest());
+  controller->LoadUrl(url, fuchsia::web::LoadUrlParams(),
+                      [](fuchsia::web::NavigationController_LoadUrl_Result) {});
+  FX_LOGF(INFO, NULL, "Loading URL: %s", url.c_str());
+
+  auth_ui_context_ = auth_ui_context.Bind();
+  auth_ui_context_.set_error_handler([this](zx_status_t status) {
+    FX_LOG(INFO, NULL, "Overlay cancelled by the caller");
+    ReleaseResources();
+    RemoveCredentialInjectorInterface();
+    SafelyCallbackGetPersistentCredential(AuthProviderStatus::UNKNOWN_ERROR,
+                                          nullptr, nullptr);
+    return;
+  });
+
+  auth_ui_context_->StartOverlay(std::move(view_holder_token));
+  ExposeCredentialInjectorInterface();
+}
+
+void GoogleAuthProviderImpl::GetAppAccessToken(
+    std::string credential, fidl::StringPtr app_client_id,
+    const std::vector<std::string> app_scopes,
+    GetAppAccessTokenCallback callback) {
+  if (credential.empty()) {
+    callback(AuthProviderStatus::BAD_REQUEST, nullptr);
+    return;
+  }
+
+  auto request =
+      OAuthRequestBuilder(kGoogleOAuthTokenEndpoint, "POST")
+          .SetUrlEncodedBody("refresh_token=" + credential +
+                             "&client_id=" + GetClientId(app_client_id.get()) +
+                             "&grant_type=refresh_token");
+
+  auto request_factory = [request = std::move(request)] {
+    return request.Build();
+  };
+
+  Request(std::move(request_factory),
+          [callback = std::move(callback)](http::URLResponse response) {
+            auto oauth_response = ParseOAuthResponse(std::move(response));
+            if (oauth_response.status != AuthProviderStatus::OK) {
+              LogOauthResponse("GetAppAccessToken", oauth_response);
+              callback(oauth_response.status, nullptr);
+              return;
+            }
+
+            const char kRootSchema[] = R"({
+              "type": "object",
+              "properties": {
+                "access_token": {
+                  "type": "string"
+                },
+                "expires_in": {
+                  "type": "integer"
+                }
+              },
+              "required": ["access_token", "expires_in"]
+            })";
+            auto root_schema = rapidjson_utils::InitSchema(kRootSchema);
+            if (!root_schema) {
+              callback(AuthProviderStatus::INTERNAL_ERROR, nullptr);
+              return;
+            }
+            if (!rapidjson_utils::ValidateSchema(oauth_response.json_response,
+                                                 *root_schema)) {
+              callback(AuthProviderStatus::OAUTH_SERVER_ERROR, nullptr);
+              return;
+            }
+
+            AuthTokenPtr access_token = fuchsia::auth::AuthToken::New();
+            access_token->token_type = fuchsia::auth::TokenType::ACCESS_TOKEN;
+            access_token->token =
+                oauth_response.json_response["access_token"].GetString();
+            access_token->expires_in =
+                oauth_response.json_response["expires_in"].GetUint64();
+
+            callback(AuthProviderStatus::OK, std::move(access_token));
+          });
+}
+
+void GoogleAuthProviderImpl::GetAppIdToken(std::string credential,
+                                           fidl::StringPtr audience,
+                                           GetAppIdTokenCallback callback) {
+  if (credential.empty()) {
+    callback(AuthProviderStatus::BAD_REQUEST, nullptr);
+    return;
+  }
+
+  auto request =
+      OAuthRequestBuilder(kGoogleOAuthTokenEndpoint, "POST")
+          .SetUrlEncodedBody("refresh_token=" + credential +
+                             "&client_id=" + GetClientId(audience.get()) +
+                             "&grant_type=refresh_token");
+
+  auto request_factory = [request = std::move(request)] {
+    return request.Build();
+  };
+  Request(std::move(request_factory),
+          [callback = std::move(callback)](http::URLResponse response) {
+            auto oauth_response = ParseOAuthResponse(std::move(response));
+            if (oauth_response.status != AuthProviderStatus::OK) {
+              LogOauthResponse("GetAppIdToken", oauth_response);
+              callback(oauth_response.status, nullptr);
+              return;
+            }
+
+            const char kRootSchema[] = R"({
+              "type": "object",
+              "properties": {
+                "id_token": {
+                  "type": "string"
+                },
+                "expires_in": {
+                  "type": "integer"
+                }
+              },
+              "required": ["id_token", "expires_in"]
+            })";
+            auto root_schema = rapidjson_utils::InitSchema(kRootSchema);
+            if (!root_schema) {
+              callback(AuthProviderStatus::INTERNAL_ERROR, nullptr);
+              return;
+            }
+            if (!rapidjson_utils::ValidateSchema(oauth_response.json_response,
+                                                 *root_schema)) {
+              callback(AuthProviderStatus::OAUTH_SERVER_ERROR, nullptr);
+              return;
+            }
+
+            AuthTokenPtr id_token = fuchsia::auth::AuthToken::New();
+            id_token->token =
+                oauth_response.json_response["id_token"].GetString();
+            id_token->token_type = fuchsia::auth::TokenType::ID_TOKEN;
+            id_token->expires_in =
+                oauth_response.json_response["expires_in"].GetUint64();
+
+            callback(AuthProviderStatus::OK, std::move(id_token));
+          });
+}
+
+void GoogleAuthProviderImpl::GetAppFirebaseToken(
+    std::string id_token, std::string firebase_api_key,
+    GetAppFirebaseTokenCallback callback) {
+  if (id_token.empty() || firebase_api_key.empty()) {
+    callback(AuthProviderStatus::BAD_REQUEST, nullptr);
+    return;
+  }
+
+  std::map<std::string, std::string> query_params;
+  query_params["key"] = firebase_api_key;
+  auto request = OAuthRequestBuilder(kFirebaseAuthEndpoint, "POST")
+                     .SetQueryParams(query_params)
+                     .SetJsonBody(R"({"postBody": "id_token=)" + id_token +
+                                  R"(&providerId=google.com",)" +
+                                  R"("returnIdpCredential": true,)" +
+                                  R"("returnSecureToken": true,)" +
+                                  R"("requestUri": "http://localhost"})");
+
+  // Exchange credential to access token at Google OAuth token endpoint
+  auto request_factory = [request = std::move(request)] {
+    return request.Build();
+  };
+  Request(std::move(request_factory), [callback = std::move(callback)](
+                                          http::URLResponse response) {
+    auto oauth_response = ParseOAuthResponse(std::move(response));
+    if (oauth_response.status != AuthProviderStatus::OK) {
+      LogOauthResponse("GetAppFirebaseToken", oauth_response);
+      callback(oauth_response.status, nullptr);
+      return;
+    }
+
+    const char kRootSchema[] = R"({
+      "type": "object",
+      "properties": {
+        "idToken": {
+          "type": "string"
+        },
+        "email": {
+          "type": "string"
+        },
+        "localId": {
+          "type": "string"
+        },
+        "expiresIn": {
+          "type": "string"
+        }
+      },
+      "required": ["idToken", "email", "localId", "expiresIn"]
+    })";
+    auto root_schema = rapidjson_utils::InitSchema(kRootSchema);
+    if (!root_schema) {
+      callback(AuthProviderStatus::INTERNAL_ERROR, nullptr);
+      return;
+    }
+    if (!rapidjson_utils::ValidateSchema(oauth_response.json_response,
+                                         *root_schema)) {
+      callback(AuthProviderStatus::OAUTH_SERVER_ERROR, nullptr);
+      return;
+    }
+
+    FirebaseTokenPtr fb_token = fuchsia::auth::FirebaseToken::New();
+    fb_token->id_token = oauth_response.json_response["idToken"].GetString();
+    fb_token->email = oauth_response.json_response["email"].GetString();
+    fb_token->local_id = oauth_response.json_response["localId"].GetString();
+    fb_token->expires_in =
+        std::atoll(oauth_response.json_response["expiresIn"].GetString());
+
+    callback(AuthProviderStatus::OK, std::move(fb_token));
+  });
+}
+
+void GoogleAuthProviderImpl::RevokeAppOrPersistentCredential(
+    std::string credential, RevokeAppOrPersistentCredentialCallback callback) {
+  if (credential.empty()) {
+    callback(AuthProviderStatus::BAD_REQUEST);
+    return;
+  }
+
+  std::string url =
+      kGoogleRevokeTokenEndpoint + std::string("?token=") + credential;
+  auto request = OAuthRequestBuilder(url, "POST").SetUrlEncodedBody("");
+
+  auto request_factory = [request = std::move(request)] {
+    return request.Build();
+  };
+
+  Request(std::move(request_factory),
+          [callback = std::move(callback)](http::URLResponse response) {
+            auto oauth_response = ParseOAuthResponse(std::move(response));
+            if (oauth_response.status != AuthProviderStatus::OK) {
+              LogOauthResponse("RevokeToken", oauth_response);
+              callback(oauth_response.status);
+              return;
+            }
+
+            callback(AuthProviderStatus::OK);
+          });
+}
+
+void GoogleAuthProviderImpl::GetPersistentCredentialFromAttestationJWT(
+    fidl::InterfaceHandle<AttestationSigner> attestation_signer,
+    AttestationJWTParams jwt_params,
+    fidl::InterfaceHandle<AuthenticationUIContext> auth_ui_context,
+    fidl::StringPtr user_profile_id,
+    GetPersistentCredentialFromAttestationJWTCallback callback) {
+  // Remote attestation flow not supported for traditional OAuth.
+  callback(AuthProviderStatus::BAD_REQUEST, nullptr, nullptr, nullptr, nullptr);
+}
+
+void GoogleAuthProviderImpl::GetAppAccessTokenFromAssertionJWT(
+    fidl::InterfaceHandle<AttestationSigner> attestation_signer,
+    AssertionJWTParams jwt_params, std::string credential,
+    const std::vector<std::string> scopes,
+    GetAppAccessTokenFromAssertionJWTCallback callback) {
+  // Remote attestation flow not supported for traditional OAuth.
+  callback(AuthProviderStatus::BAD_REQUEST, nullptr, nullptr, nullptr);
+}
+
+void GoogleAuthProviderImpl::OnNavigationStateChanged(
+    NavigationState change, OnNavigationStateChangedCallback callback) {
+  // Not all events change the URL, those that don't can be ignored.
+  if (!change.has_url()) {
+    callback();
+    return;
+  }
+
+  std::string auth_code;
+  AuthProviderStatus status = ParseAuthCodeFromUrl(change.url(), auth_code);
+
+  // If either an error occured or the user successfully received an auth code
+  // we need to close the browser instance.
+  if (status != AuthProviderStatus::OK || !auth_code.empty()) {
+    ReleaseResources();
+    // InjectPersistentCredential will still be reachable even after removing
+    // the interface from output, but any requests to it will be discarded.
+    RemoveCredentialInjectorInterface();
+    if (status != AuthProviderStatus::OK) {
+      FX_LOGF(INFO, NULL, "Failed to capture auth code: Status %d", status);
+      SafelyCallbackGetPersistentCredential(status, nullptr, nullptr);
+    } else if (!auth_code.empty()) {
+      FX_LOGF(INFO, NULL, "Captured auth code of length %d", auth_code.size());
+      ExchangeAuthCode(auth_code);
+    }
+  }
+
+  callback();
+}
+
+void GoogleAuthProviderImpl::InjectPersistentCredential(
+    fuchsia::auth::UserProfileInfoPtr user_profile_info,
+    std::string credential) {
+  ReleaseResources();
+  RemoveCredentialInjectorInterface();
+  FX_LOGF(INFO, NULL, "Received injection request with credential of length %d",
+          credential.size());
+  SafelyCallbackGetPersistentCredential(AuthProviderStatus::OK,
+                                        std::move(credential),
+                                        std::move(user_profile_info));
+}
+
+std::string GoogleAuthProviderImpl::GetAuthorizeUrl(
+    fidl::StringPtr user_profile) {
+  // TODO(ukode,jsankey): use app_scopes instead of |kScopes|.
+  const std::vector<std::string> scopes(kScopes.begin(), kScopes.end());
+  std::string scopes_str = fxl::JoinStrings(scopes, "+");
+
+  std::string url = settings_.use_dedicated_endpoint ? kGoogleFuchsiaEndpoint
+                                                     : kGoogleOAuthAuthEndpoint;
+  url += "?scope=" + scopes_str;
+  url += "&glif=";
+  url += settings_.use_glif ? "true" : "false";
+  url += "&response_type=code&redirect_uri=";
+  url += kRedirectUri;
+  url += "&client_id=";
+  url += kFuchsiaClientId;
+  // TODO(ukode,jsankey): Set user_profile_id in the state query arg for re-auth
+  // This probably involves moving the current implementation of UrlEncoding in
+  // OAuthRequestBuilder to a reusable library and using this to urlencode the
+  // supplied user_profile into the login_hint query parameter.
+  return url;
+}
+
+void GoogleAuthProviderImpl::ExchangeAuthCode(std::string auth_code) {
+  auto request = OAuthRequestBuilder(kGoogleOAuthTokenEndpoint, "POST")
+                     .SetUrlEncodedBody("code=" + auth_code +
+                                        "&redirect_uri=" + kRedirectUri +
+                                        "&client_id=" + kFuchsiaClientId +
+                                        "&grant_type=authorization_code");
+
+  auto request_factory = [request = std::move(request)] {
+    return request.Build();
+  };
+
+  Request(std::move(request_factory), [this](http::URLResponse response) {
+    auto oauth_response = ParseOAuthResponse(std::move(response));
+    if (oauth_response.status != AuthProviderStatus::OK) {
+      LogOauthResponse("ExchangeAuthCode", oauth_response);
+      SafelyCallbackGetPersistentCredential(oauth_response.status, nullptr,
+                                            nullptr);
+      return;
+    }
+
+    const char kRootSchema[] = R"({
+      "type": "object",
+      "properties": {
+        "refresh_token": {
+          "type": "string"
+        },
+        "access_token": {
+          "type": "string"
+        }
+      },
+      "required": ["refresh_token", "access_token"]
+    })";
+    auto root_schema = rapidjson_utils::InitSchema(kRootSchema);
+    if (!root_schema) {
+      SafelyCallbackGetPersistentCredential(AuthProviderStatus::INTERNAL_ERROR,
+                                            nullptr, nullptr);
+      return;
+    }
+    if (!rapidjson_utils::ValidateSchema(oauth_response.json_response,
+                                         *root_schema)) {
+      FX_LOGF(WARNING, NULL,
+              "Got response without refresh and access tokens: %s",
+              JsonValueToPrettyString(oauth_response.json_response).c_str());
+      SafelyCallbackGetPersistentCredential(
+          AuthProviderStatus::OAUTH_SERVER_ERROR, nullptr, nullptr);
+      return;
+    }
+
+    auto refresh_token =
+        oauth_response.json_response["refresh_token"].GetString();
+    auto access_token =
+        oauth_response.json_response["access_token"].GetString();
+    FX_LOGF(INFO, NULL, "Received refresh token of length %d",
+            strlen(refresh_token));
+
+    GetUserProfile(refresh_token, access_token);
+  });
+}
+
+void GoogleAuthProviderImpl::GetUserProfile(fidl::StringPtr credential,
+                                            fidl::StringPtr access_token) {
+  FXL_DCHECK(credential.get().size() > 0);
+  FXL_DCHECK(access_token.get().size() > 0);
+
+  auto request = OAuthRequestBuilder(kGoogleUserInfoEndpoint, "GET")
+                     .SetAuthorizationHeader(access_token.get());
+
+  auto request_factory = [request = std::move(request)] {
+    return request.Build();
+  };
+
+  Request(std::move(request_factory), [this,
+                                       credential](http::URLResponse response) {
+    fuchsia::auth::UserProfileInfoPtr user_profile_info =
+        fuchsia::auth::UserProfileInfo::New();
+
+    auto oauth_response = ParseOAuthResponse(std::move(response));
+    if (oauth_response.status != AuthProviderStatus::OK) {
+      LogOauthResponse("UserInfo", oauth_response);
+      SafelyCallbackGetPersistentCredential(oauth_response.status, credential,
+                                            std::move(user_profile_info));
+      return;
+    }
+
+    if (oauth_response.json_response.HasMember("sub") &&
+        oauth_response.json_response["sub"].IsString()) {
+      user_profile_info->id = oauth_response.json_response["sub"].GetString();
+    } else {
+      LogOauthResponse("UserInfo", oauth_response);
+      FX_LOG(INFO, NULL, "Missing unique identifier in UserInfo response");
+      SafelyCallbackGetPersistentCredential(
+          AuthProviderStatus::OAUTH_SERVER_ERROR, nullptr,
+          std::move(user_profile_info));
+      return;
+    }
+
+    if (oauth_response.json_response.HasMember("name") &&
+        oauth_response.json_response["name"].IsString()) {
+      user_profile_info->display_name =
+          oauth_response.json_response["name"].GetString();
+    }
+
+    if (oauth_response.json_response.HasMember("profile") &&
+        oauth_response.json_response["profile"].IsString()) {
+      user_profile_info->url =
+          oauth_response.json_response["profile"].GetString();
+    }
+
+    if (oauth_response.json_response.HasMember("picture") &&
+        oauth_response.json_response["picture"].IsString()) {
+      user_profile_info->image_url =
+          oauth_response.json_response["picture"].GetString();
+    }
+
+    FX_LOG(INFO, NULL, "Received valid UserInfo response");
+    SafelyCallbackGetPersistentCredential(oauth_response.status, credential,
+                                          std::move(user_profile_info));
+  });
+}
+
+fuchsia::ui::views::ViewHolderToken GoogleAuthProviderImpl::SetupChromium() {
+  // Connect to the Chromium service and create a new frame.
+  auto context_provider =
+      context_->svc()->Connect<fuchsia::web::ContextProvider>();
+
+  fidl::InterfaceHandle<fuchsia::io::Directory> incoming_service_clone =
+      context_->svc()->CloneChannel();
+
+  if (!incoming_service_clone.is_valid()) {
+    FX_LOG(ERROR, NULL, "Failed to clone service directory");
+    return fuchsia::ui::views::ViewHolderToken();
+  }
+
+  fuchsia::web::CreateContextParams params;
+  params.set_service_directory(std::move(incoming_service_clone));
+  context_provider->Create(std::move(params), web_context_.NewRequest());
+  web_context_->CreateFrame(web_frame_.NewRequest());
+
+  // Bind ourselves as a NavigationEventListener on this frame.
+  fuchsia::web::NavigationEventListenerPtr navigation_event_listener;
+  navigation_event_listener_bindings_.AddBinding(
+      this, navigation_event_listener.NewRequest());
+  web_frame_->SetNavigationEventListener(std::move(navigation_event_listener));
+
+  // And create a view for the frame.
+  auto [view_token, view_holder_token] = scenic::NewViewTokenPair();
+  web_frame_->CreateView(std::move(view_token));
+
+  return std::move(view_holder_token);
+}
+
+void GoogleAuthProviderImpl::SafelyCallbackGetPersistentCredential(
+    AuthProviderStatus auth_provider_status, fidl::StringPtr credential,
+    fuchsia::auth::UserProfileInfoPtr user_profile_info) {
+  if (get_persistent_credential_callback_) {
+    get_persistent_credential_callback_(auth_provider_status,
+                                        std::move(credential),
+                                        std::move(user_profile_info));
+    get_persistent_credential_callback_ = nullptr;
+  } else {
+    FX_LOG(WARNING, NULL,
+           "Attempted to call GetPersistentCredential callback twice.");
+  }
+}
+
+void GoogleAuthProviderImpl::ReleaseResources() {
+  // Close any open view
+  if (auth_ui_context_) {
+    FX_LOG(INFO, NULL, "Releasing Auth UI Context");
+    auth_ui_context_.set_error_handler(nullptr);
+    auth_ui_context_->StopOverlay();
+    auth_ui_context_ = nullptr;
+  }
+  // Release all smart pointers for Chromium resources.
+  web_frame_ = nullptr;
+  web_context_ = nullptr;
+}
+
+void GoogleAuthProviderImpl::ExposeCredentialInjectorInterface() {
+  context_->outgoing()->debug_dir()->AddEntry(
+      kInjectionEntry,
+      std::make_unique<vfs::Service>(injector_bindings_.GetHandler(this)));
+}
+
+void GoogleAuthProviderImpl::RemoveCredentialInjectorInterface() {
+  if (context_->outgoing()->debug_dir()->RemoveEntry(kInjectionEntry) ==
+      ZX_ERR_NOT_FOUND) {
+    FX_LOGF(WARNING, NULL,
+            "Attempted to remove nonexistent '%s' from debug directory",
+            kInjectionEntry);
+  }
+}
+
+void GoogleAuthProviderImpl::Request(
+    fit::function<http::URLRequest()> request_factory,
+    fit::function<void(http::URLResponse response)> callback) {
+  requests_.emplace(network_wrapper_->Request(std::move(request_factory),
+                                              std::move(callback)));
+}
+
+}  // namespace google_auth_provider
diff --git a/src/identity/bin/google_auth_provider/google_auth_provider_impl.h b/src/identity/bin/google_auth_provider/google_auth_provider_impl.h
new file mode 100644
index 0000000..2816916
--- /dev/null
+++ b/src/identity/bin/google_auth_provider/google_auth_provider_impl.h
@@ -0,0 +1,171 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This application serves as the Google Auth provider for generating OAuth
+// credentials to talk to Google Api backends. This application implements
+// |auth_provider.fidl| interface and is typically invoked by the Token Manager
+// service in Garnet layer.
+
+#ifndef SRC_IDENTITY_BIN_GOOGLE_AUTH_PROVIDER_GOOGLE_AUTH_PROVIDER_IMPL_H_
+#define SRC_IDENTITY_BIN_GOOGLE_AUTH_PROVIDER_GOOGLE_AUTH_PROVIDER_IMPL_H_
+
+#include <fuchsia/auth/cpp/fidl.h>
+#include <fuchsia/auth/testing/cpp/fidl.h>
+#include <fuchsia/ui/views/cpp/fidl.h>
+#include <fuchsia/web/cpp/fidl.h>
+#include <lib/callback/cancellable.h>
+#include <lib/fidl/cpp/binding.h>
+#include <lib/fidl/cpp/binding_set.h>
+#include <lib/fit/function.h>
+#include <lib/network_wrapper/network_wrapper.h>
+#include <lib/sys/cpp/component_context.h>
+
+#include "src/identity/bin/google_auth_provider/settings.h"
+#include "src/lib/fxl/macros.h"
+
+namespace google_auth_provider {
+
+using fuchsia::auth::AssertionJWTParams;
+using fuchsia::auth::AttestationJWTParams;
+using fuchsia::auth::AttestationSigner;
+using fuchsia::auth::AuthenticationUIContext;
+using fuchsia::web::NavigationState;
+
+class GoogleAuthProviderImpl
+    : fuchsia::web::NavigationEventListener,
+      fuchsia::auth::AuthProvider,
+      fuchsia::auth::testing::LegacyAuthCredentialInjector {
+ public:
+  GoogleAuthProviderImpl(
+      async_dispatcher_t* main_dispatcher, sys::ComponentContext* context,
+      network_wrapper::NetworkWrapper* network_wrapper, Settings settings,
+      fidl::InterfaceRequest<fuchsia::auth::AuthProvider> request);
+
+  ~GoogleAuthProviderImpl() override;
+
+  void set_on_empty(fit::closure on_empty) { on_empty_ = std::move(on_empty); }
+
+ private:
+  // |AuthProvider|
+  void GetPersistentCredential(
+      fidl::InterfaceHandle<fuchsia::auth::AuthenticationUIContext>
+          auth_ui_context,
+      fidl::StringPtr user_profile_id,
+      GetPersistentCredentialCallback callback) override;
+
+  // |AuthProvider|
+  void GetAppAccessToken(std::string credential, fidl::StringPtr app_client_id,
+                         const std::vector<std::string> app_scopes,
+                         GetAppAccessTokenCallback callback) override;
+
+  // |AuthProvider|
+  void GetAppIdToken(std::string credential, fidl::StringPtr audience,
+                     GetAppIdTokenCallback callback) override;
+
+  // |AuthProvider|
+  void GetAppFirebaseToken(std::string id_token, std::string firebase_api_key,
+                           GetAppFirebaseTokenCallback callback) override;
+
+  // |AuthProvider|
+  void RevokeAppOrPersistentCredential(
+      std::string credential,
+      RevokeAppOrPersistentCredentialCallback callback) override;
+
+  // |AuthProvider|
+  void GetPersistentCredentialFromAttestationJWT(
+      fidl::InterfaceHandle<AttestationSigner> attestation_signer,
+      AttestationJWTParams jwt_params,
+      fidl::InterfaceHandle<AuthenticationUIContext> auth_ui_context,
+      fidl::StringPtr user_profile_id,
+      GetPersistentCredentialFromAttestationJWTCallback callback) override;
+
+  // |AuthProvider|
+  void GetAppAccessTokenFromAssertionJWT(
+      fidl::InterfaceHandle<AttestationSigner> attestation_signer,
+      AssertionJWTParams jwt_params, std::string credential,
+      std::vector<std::string> scopes,
+      GetAppAccessTokenFromAssertionJWTCallback callback) override;
+
+  // |fuchsia::web::NavigationEventListener|
+  void OnNavigationStateChanged(
+      NavigationState change,
+      OnNavigationStateChangedCallback callback) override;
+
+  // |fuchsia::auth::testing::LegacyAuthCredentialInjector|
+  // This is a short-term solution to enable end-to-end testing.  It should not
+  // be carried over during any refactoring efforts.
+  void InjectPersistentCredential(
+      fuchsia::auth::UserProfileInfoPtr user_profile_info,
+      std::string credential) override;
+
+  // Returns the URL to be used for the authentication call, respecting any
+  // settings that influence the URL.
+  std::string GetAuthorizeUrl(fidl::StringPtr user_profile_id);
+
+  // Calls the OAuth auth endpoint to exchange the supplied |auth_code| for a
+  // long term credential, and then calls |GetUserProfile| with that credential.
+  // If any errors are encountered a failure status is returned on the pending
+  // |get_credential_callback_|.
+  void ExchangeAuthCode(std::string auth_code);
+
+  // Calls the people endpoint to gather profile information using the supplied
+  // |access_token| and responds to the pending |get_credential_callback_|.
+  void GetUserProfile(fidl::StringPtr credential, fidl::StringPtr access_token);
+
+  // Launches and connects to a Chromium frame, binding |this| as a
+  // |NavigationEventListener| to process any changes in the URL, and returning
+  // a |fuchsia::ui::views::ViewHolderToken| token for the view's ViewHolder.
+  fuchsia::ui::views::ViewHolderToken SetupChromium();
+
+  // Calls the GetPersistentCredential callback if one is available, or logs
+  // and returns immediately otherwise.  This enables interactive signin or
+  // InjectPersistentCredential to terminate gracefully even after the other
+  // has sent a response to the pending callback.
+  void SafelyCallbackGetPersistentCredential(
+      fuchsia::auth::AuthProviderStatus auth_provider_status,
+      fidl::StringPtr credential,
+      fuchsia::auth::UserProfileInfoPtr user_profile_info);
+
+  // Exposes a |fuchsia::auth::testing::LegacyAuthCredentialInjector| handle on
+  // the output debug directory.
+  void ExposeCredentialInjectorInterface();
+
+  // Removes the |fuchsia::auth::testing::LegacyAuthCredentialInjector| handle
+  // on the output debug directory.
+  void RemoveCredentialInjectorInterface();
+
+  // Safely releases any resources associated with an open Webkit or Chromium
+  // instance, including the associated view.
+  void ReleaseResources();
+
+  void Request(
+      fit::function<::fuchsia::net::oldhttp::URLRequest()> request_factory,
+      fit::function<void(::fuchsia::net::oldhttp::URLResponse response)>
+          callback);
+
+  async_dispatcher_t* const main_dispatcher_;
+  sys::ComponentContext* context_;
+  network_wrapper::NetworkWrapper* const network_wrapper_;
+  const Settings settings_;
+  fuchsia::sys::ComponentControllerPtr web_view_controller_;
+  fuchsia::auth::AuthenticationUIContextPtr auth_ui_context_;
+  fuchsia::web::ContextPtr web_context_;
+  fuchsia::web::FramePtr web_frame_;
+  GetPersistentCredentialCallback get_persistent_credential_callback_;
+
+  fidl::BindingSet<fuchsia::web::NavigationEventListener>
+      navigation_event_listener_bindings_;
+  fidl::BindingSet<fuchsia::auth::testing::LegacyAuthCredentialInjector>
+      injector_bindings_;
+  fidl::Binding<fuchsia::auth::AuthProvider> binding_;
+  callback::CancellableContainer requests_;
+
+  fit::closure on_empty_;
+
+  FXL_DISALLOW_COPY_AND_ASSIGN(GoogleAuthProviderImpl);
+};
+
+}  // namespace google_auth_provider
+
+#endif  // SRC_IDENTITY_BIN_GOOGLE_AUTH_PROVIDER_GOOGLE_AUTH_PROVIDER_IMPL_H_
diff --git a/src/identity/bin/google_auth_provider/google_auth_provider_impl_unittest.cc b/src/identity/bin/google_auth_provider/google_auth_provider_impl_unittest.cc
new file mode 100644
index 0000000..7b398c7
--- /dev/null
+++ b/src/identity/bin/google_auth_provider/google_auth_provider_impl_unittest.cc
@@ -0,0 +1,285 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/identity/bin/google_auth_provider/google_auth_provider_impl.h"
+
+#include "lib/callback/capture.h"
+#include "lib/callback/set_when_called.h"
+#include "lib/fidl/cpp/binding.h"
+#include "lib/gtest/test_loop_fixture.h"
+#include "lib/network_wrapper/fake_network_wrapper.h"
+#include "peridot/lib/rapidjson/rapidjson.h"
+#include "src/identity/bin/google_auth_provider/settings.h"
+#include "src/lib/fxl/macros.h"
+
+namespace google_auth_provider {
+namespace {
+
+using fuchsia::auth::AuthProviderStatus;
+using fuchsia::auth::AuthTokenPtr;
+
+class GoogleAuthProviderImplTest : public gtest::TestLoopFixture {
+ public:
+  GoogleAuthProviderImplTest()
+      : network_wrapper_(dispatcher()),
+        context_(sys::ComponentContext::Create().get()),
+        google_auth_provider_impl_(dispatcher(), context_, &network_wrapper_,
+                                   {}, auth_provider_.NewRequest()) {}
+
+  ~GoogleAuthProviderImplTest() override {}
+
+ protected:
+  network_wrapper::FakeNetworkWrapper network_wrapper_;
+  sys::ComponentContext* context_;
+  fuchsia::auth::AuthProviderPtr auth_provider_;
+  GoogleAuthProviderImpl google_auth_provider_impl_;
+
+ private:
+  FXL_DISALLOW_COPY_AND_ASSIGN(GoogleAuthProviderImplTest);
+};
+
+TEST_F(GoogleAuthProviderImplTest, EmptyWhenClientDisconnected) {
+  bool on_empty_called = false;
+  google_auth_provider_impl_.set_on_empty([this, &on_empty_called] {
+    on_empty_called = true;
+    QuitLoop();
+  });
+  auth_provider_.Unbind();
+  RunLoopUntilIdle();
+  EXPECT_TRUE(on_empty_called);
+}
+
+TEST_F(GoogleAuthProviderImplTest, GetAppAccessTokenSuccess) {
+  bool callback_called = false;
+  auto status = AuthProviderStatus::INTERNAL_ERROR;
+  AuthTokenPtr access_token;
+  std::vector<std::string> scopes;
+  scopes.push_back("https://www.googleapis.com/auth/gmail.modify");
+  scopes.push_back("https://www.googleapis.com/auth/userinfo.email");
+
+  rapidjson::Document ok_response;
+  ok_response.Parse(
+      "{\"access_token\":\"test_at_token\", \"expires_in\":3600}");
+  network_wrapper_.SetStringResponse(
+      modular::JsonValueToPrettyString(ok_response), 200);
+
+  auth_provider_->GetAppAccessToken(
+      "credential", "test_client_id", std::move(scopes),
+      callback::Capture(callback::SetWhenCalled(&callback_called), &status,
+                        &access_token));
+
+  RunLoopUntilIdle();
+  EXPECT_TRUE(callback_called);
+  EXPECT_EQ(status, AuthProviderStatus::OK);
+  EXPECT_FALSE(access_token == NULL);
+  EXPECT_EQ(access_token->token_type, fuchsia::auth::TokenType::ACCESS_TOKEN);
+  EXPECT_EQ(access_token->token, "test_at_token");
+  EXPECT_EQ(access_token->expires_in, 3600u);
+}
+
+TEST_F(GoogleAuthProviderImplTest, GetAppAccessTokenBadRequestError) {
+  bool callback_called = false;
+  auto status = AuthProviderStatus::INTERNAL_ERROR;
+  fuchsia::auth::AuthTokenPtr access_token;
+  std::vector<std::string> scopes;
+  scopes.push_back("https://www.googleapis.com/auth/gmail.modify");
+
+  auth_provider_->GetAppAccessToken(
+      "", "", std::move(scopes),
+      callback::Capture(callback::SetWhenCalled(&callback_called), &status,
+                        &access_token));
+
+  RunLoopUntilIdle();
+  EXPECT_TRUE(callback_called);
+  EXPECT_EQ(status, AuthProviderStatus::BAD_REQUEST);
+  EXPECT_TRUE(access_token == NULL);
+}
+
+TEST_F(GoogleAuthProviderImplTest, GetAppAccessTokenInvalidClientError) {
+  bool callback_called = false;
+  auto status = AuthProviderStatus::INTERNAL_ERROR;
+  AuthTokenPtr access_token;
+  std::vector<std::string> scopes;
+  scopes.push_back("https://www.googleapis.com/auth/gmail.modify");
+  scopes.push_back("https://www.googleapis.com/auth/userinfo.email");
+
+  rapidjson::Document ok_response;
+  ok_response.Parse("{\"error\":\"invalid_client\"}");
+  network_wrapper_.SetStringResponse(
+      modular::JsonValueToPrettyString(ok_response), 401);
+
+  auth_provider_->GetAppAccessToken(
+      "credential", "invalid_client_id", std::move(scopes),
+      callback::Capture(callback::SetWhenCalled(&callback_called), &status,
+                        &access_token));
+
+  RunLoopUntilIdle();
+  EXPECT_TRUE(callback_called);
+  EXPECT_EQ(status, AuthProviderStatus::OAUTH_SERVER_ERROR);
+  EXPECT_TRUE(access_token == NULL);
+}
+
+TEST_F(GoogleAuthProviderImplTest, GetAppAccessTokenInvalidUserError) {
+  bool callback_called = false;
+  auto status = AuthProviderStatus::INTERNAL_ERROR;
+  AuthTokenPtr access_token;
+  std::vector<std::string> scopes;
+  scopes.push_back("https://www.googleapis.com/auth/gmail.modify");
+  scopes.push_back("https://www.googleapis.com/auth/userinfo.email");
+
+  rapidjson::Document ok_response;
+  ok_response.Parse("{\"error\":\"invalid_credential\"}");
+  network_wrapper_.SetStringResponse(
+      modular::JsonValueToPrettyString(ok_response), 401);
+
+  auth_provider_->GetAppAccessToken(
+      "invalid_credential", "test_client_id", std::move(scopes),
+      callback::Capture(callback::SetWhenCalled(&callback_called), &status,
+                        &access_token));
+
+  RunLoopUntilIdle();
+  EXPECT_TRUE(callback_called);
+  EXPECT_EQ(status, AuthProviderStatus::OAUTH_SERVER_ERROR);
+  EXPECT_TRUE(access_token == NULL);
+}
+
+TEST_F(GoogleAuthProviderImplTest, GetAppIdTokenSuccess) {
+  bool callback_called = false;
+  auto status = AuthProviderStatus::INTERNAL_ERROR;
+  AuthTokenPtr id_token;
+
+  rapidjson::Document ok_response;
+  ok_response.Parse("{\"id_token\":\"test_id_token\", \"expires_in\":3600}");
+  network_wrapper_.SetStringResponse(
+      modular::JsonValueToPrettyString(ok_response), 200);
+
+  auth_provider_->GetAppIdToken(
+      "credential", "test_audience",
+      callback::Capture(callback::SetWhenCalled(&callback_called), &status,
+                        &id_token));
+
+  RunLoopUntilIdle();
+  EXPECT_TRUE(callback_called);
+  EXPECT_EQ(status, AuthProviderStatus::OK);
+  EXPECT_FALSE(id_token == NULL);
+  EXPECT_EQ(id_token->token_type, fuchsia::auth::TokenType::ID_TOKEN);
+  EXPECT_EQ(id_token->token, "test_id_token");
+  EXPECT_EQ(id_token->expires_in, 3600u);
+}
+
+TEST_F(GoogleAuthProviderImplTest, GetAppIdTokenBadRequestError) {
+  bool callback_called = false;
+  auto status = AuthProviderStatus::INTERNAL_ERROR;
+  AuthTokenPtr id_token;
+
+  auth_provider_->GetAppIdToken(
+      "", "test_audience",
+      callback::Capture(callback::SetWhenCalled(&callback_called), &status,
+                        &id_token));
+  RunLoopUntilIdle();
+  EXPECT_TRUE(callback_called);
+  EXPECT_EQ(status, AuthProviderStatus::BAD_REQUEST);
+  EXPECT_TRUE(id_token == NULL);
+}
+
+TEST_F(GoogleAuthProviderImplTest, GetAppIdTokenInvalidAudienceError) {
+  bool callback_called = false;
+  auto status = AuthProviderStatus::INTERNAL_ERROR;
+  AuthTokenPtr id_token;
+
+  rapidjson::Document ok_response;
+  ok_response.Parse("{\"error\":\"invalid_client\"}");
+  network_wrapper_.SetStringResponse(
+      modular::JsonValueToPrettyString(ok_response), 401);
+
+  auth_provider_->GetAppIdToken(
+      "credential", "invalid_audience",
+      callback::Capture(callback::SetWhenCalled(&callback_called), &status,
+                        &id_token));
+
+  RunLoopUntilIdle();
+  EXPECT_TRUE(callback_called);
+  EXPECT_EQ(status, AuthProviderStatus::OAUTH_SERVER_ERROR);
+  EXPECT_TRUE(id_token == NULL);
+}
+
+TEST_F(GoogleAuthProviderImplTest, GetAppIdTokenInvalidUserError) {
+  bool callback_called = false;
+  auto status = AuthProviderStatus::INTERNAL_ERROR;
+  AuthTokenPtr id_token;
+
+  rapidjson::Document ok_response;
+  ok_response.Parse("{\"error\":\"invalid_credential\"}");
+  network_wrapper_.SetStringResponse(
+      modular::JsonValueToPrettyString(ok_response), 401);
+
+  auth_provider_->GetAppIdToken(
+      "invalid_credential", "audience",
+      callback::Capture(callback::SetWhenCalled(&callback_called), &status,
+                        &id_token));
+
+  RunLoopUntilIdle();
+  EXPECT_TRUE(callback_called);
+  EXPECT_EQ(status, AuthProviderStatus::OAUTH_SERVER_ERROR);
+  EXPECT_TRUE(id_token == NULL);
+}
+
+TEST_F(GoogleAuthProviderImplTest, GetAppFirebaseTokenSuccess) {
+  bool callback_called = false;
+  auto status = AuthProviderStatus::INTERNAL_ERROR;
+  fuchsia::auth::FirebaseTokenPtr fb_token;
+
+  rapidjson::Document ok_response;
+  ok_response.Parse(
+      "{\"idToken\":\"test_fb_token\", \"localId\":\"test123\",\
+                      \"email\":\"foo@example.com\", \"expiresIn\":\"3600\"}");
+  network_wrapper_.SetStringResponse(
+      modular::JsonValueToPrettyString(ok_response), 200);
+
+  auth_provider_->GetAppFirebaseToken(
+      "test_id_token", "test_firebase_api_key",
+      callback::Capture(callback::SetWhenCalled(&callback_called), &status,
+                        &fb_token));
+
+  RunLoopUntilIdle();
+  EXPECT_TRUE(callback_called);
+  EXPECT_EQ(status, AuthProviderStatus::OK);
+  EXPECT_FALSE(fb_token == NULL);
+  EXPECT_EQ(fb_token->id_token, "test_fb_token");
+  EXPECT_EQ(fb_token->local_id, "test123");
+  EXPECT_EQ(fb_token->email, "foo@example.com");
+  EXPECT_EQ(fb_token->expires_in, 3600u);
+}
+
+TEST_F(GoogleAuthProviderImplTest, GetAppFirebaseTokenBadRequestError) {
+  bool callback_called = false;
+  auto status = AuthProviderStatus::INTERNAL_ERROR;
+  fuchsia::auth::FirebaseTokenPtr fb_token;
+
+  auth_provider_->GetAppFirebaseToken(
+      "", "",
+      callback::Capture(callback::SetWhenCalled(&callback_called), &status,
+                        &fb_token));
+
+  RunLoopUntilIdle();
+  EXPECT_TRUE(callback_called);
+  EXPECT_EQ(status, AuthProviderStatus::BAD_REQUEST);
+  EXPECT_TRUE(fb_token == NULL);
+}
+
+TEST_F(GoogleAuthProviderImplTest, RevokeAppOrPersistentCredentialUnsupported) {
+  bool callback_called = false;
+  auto status = AuthProviderStatus::INTERNAL_ERROR;
+
+  auth_provider_->RevokeAppOrPersistentCredential(
+      "",
+      callback::Capture(callback::SetWhenCalled(&callback_called), &status));
+
+  RunLoopUntilIdle();
+  EXPECT_TRUE(callback_called);
+  EXPECT_EQ(status, AuthProviderStatus::BAD_REQUEST);
+}
+
+}  // namespace
+}  // namespace google_auth_provider
diff --git a/src/identity/bin/google_auth_provider/main.cc b/src/identity/bin/google_auth_provider/main.cc
new file mode 100644
index 0000000..2cba0c97
--- /dev/null
+++ b/src/identity/bin/google_auth_provider/main.cc
@@ -0,0 +1,89 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuchsia/auth/cpp/fidl.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/sys/cpp/component_context.h>
+#include <trace-provider/provider.h>
+
+#include <memory>
+
+#include "lib/backoff/exponential_backoff.h"
+#include "lib/fidl/cpp/binding_set.h"
+#include "lib/fidl/cpp/interface_request.h"
+#include "lib/fsl/syslogger/init.h"
+#include "lib/fsl/vmo/strings.h"
+#include "lib/network_wrapper/network_wrapper_impl.h"
+#include "src/identity/bin/google_auth_provider/factory_impl.h"
+#include "src/identity/bin/google_auth_provider/settings.h"
+#include "src/lib/fxl/command_line.h"
+
+namespace {
+
+namespace http = ::fuchsia::net::oldhttp;
+using fuchsia::auth::AuthProviderFactory;
+using google_auth_provider::Settings;
+
+class GoogleAuthProviderApp {
+ public:
+  GoogleAuthProviderApp(fxl::CommandLine command_line)
+      : loop_(&kAsyncLoopConfigAttachToThread),
+        component_context_(sys::ComponentContext::Create()),
+        trace_provider_(loop_.dispatcher()),
+        network_wrapper_(
+            loop_.dispatcher(), std::make_unique<backoff::ExponentialBackoff>(),
+            [this] {
+              return component_context_->svc()->Connect<http::HttpService>();
+            }),
+        factory_impl_(loop_.dispatcher(), component_context_.get(),
+                      &network_wrapper_, CreateSettings(command_line)) {
+    FXL_DCHECK(component_context_);
+  }
+
+  ~GoogleAuthProviderApp() { loop_.Quit(); }
+
+  void Run() {
+    component_context_->outgoing()->AddPublicService<AuthProviderFactory>(
+        [this](fidl::InterfaceRequest<AuthProviderFactory> request) {
+          factory_impl_.Bind(std::move(request));
+        });
+    loop_.Run();
+  }
+
+ private:
+  async::Loop loop_;
+  std::unique_ptr<sys::ComponentContext> component_context_;
+  trace::TraceProvider trace_provider_;
+  network_wrapper::NetworkWrapperImpl network_wrapper_;
+  google_auth_provider::FactoryImpl factory_impl_;
+
+  static Settings CreateSettings(fxl::CommandLine command_line) {
+    Settings settings;
+    if (command_line.HasOption("glif")) {
+      settings.use_glif = true;
+    } else if (command_line.HasOption("redcarpet")) {
+      settings.use_glif = false;
+    }
+    if (command_line.HasOption("fuchsiaendpoint")) {
+      settings.use_dedicated_endpoint = true;
+    } else if (command_line.HasOption("oauthendpoint")) {
+      settings.use_dedicated_endpoint = false;
+    }
+    return settings;
+  }
+
+  FXL_DISALLOW_COPY_AND_ASSIGN(GoogleAuthProviderApp);
+};
+
+}  // namespace
+
+int main(int argc, const char** argv) {
+  auto command_line = fxl::CommandLineFromArgcArgv(argc, argv);
+  fsl::InitLoggerFromCommandLine(command_line, {"auth"});
+
+  GoogleAuthProviderApp app(command_line);
+  app.Run();
+
+  return 0;
+}
diff --git a/src/identity/bin/google_auth_provider/meta/google_auth_provider_2.cmx b/src/identity/bin/google_auth_provider/meta/google_auth_provider_2.cmx
new file mode 100644
index 0000000..2731608
--- /dev/null
+++ b/src/identity/bin/google_auth_provider/meta/google_auth_provider_2.cmx
@@ -0,0 +1,22 @@
+{
+    "program": {
+        "binary": "bin/app"
+    },
+    "sandbox": {
+        "services": [
+            "fuchsia.fonts.Provider",
+            "fuchsia.media.Audio",
+            "fuchsia.net.SocketProvider",
+            "fuchsia.net.oldhttp.HttpService",
+            "fuchsia.netstack.Netstack",
+            "fuchsia.process.Launcher",
+            "fuchsia.sys.Launcher",
+            "fuchsia.ui.input.ImeService",
+            "fuchsia.ui.input.ImeVisibilityService",
+            "fuchsia.ui.scenic.Scenic",
+            "fuchsia.logger.LogSink",
+            "fuchsia.tracelink.Registry",
+            "fuchsia.web.ContextProvider"
+        ]
+    }
+}
diff --git a/src/identity/bin/google_auth_provider/settings.h b/src/identity/bin/google_auth_provider/settings.h
new file mode 100644
index 0000000..7439a52
--- /dev/null
+++ b/src/identity/bin/google_auth_provider/settings.h
@@ -0,0 +1,21 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_IDENTITY_BIN_GOOGLE_AUTH_PROVIDER_SETTINGS_H_
+#define SRC_IDENTITY_BIN_GOOGLE_AUTH_PROVIDER_SETTINGS_H_
+
+namespace google_auth_provider {
+
+struct Settings {
+  // Set true to request the "GLIF" UI style. The default of false will request
+  // the legacy "RedCarpet" UI style.
+  bool use_glif = false;
+  // Set true to connect to a dedicated authentication endpoint for Fuchsia
+  // instead of the standard OAuth endpoint.
+  bool use_dedicated_endpoint = false;
+};
+
+}  // namespace google_auth_provider
+
+#endif  // SRC_IDENTITY_BIN_GOOGLE_AUTH_PROVIDER_SETTINGS_H_
diff --git a/src/identity/bin/meta/google_auth_provider_unittests.cmx b/src/identity/bin/meta/google_auth_provider_unittests.cmx
new file mode 100644
index 0000000..0114639
--- /dev/null
+++ b/src/identity/bin/meta/google_auth_provider_unittests.cmx
@@ -0,0 +1,8 @@
+{
+    "program": {
+        "binary": "test/google_auth_provider_unittests"
+    },
+    "sandbox": {
+        "services": []
+    }
+}
diff --git a/src/identity/lib/BUILD.gn b/src/identity/lib/BUILD.gn
index d12eb2f..543898d 100644
--- a/src/identity/lib/BUILD.gn
+++ b/src/identity/lib/BUILD.gn
@@ -21,6 +21,7 @@
 
 test_package("identity_lib_unittests") {
   deps = [
+    "oauth:oauth_unittests",
     "token_cache:token_cache",
     "token_store:token_store",
   ]
@@ -34,5 +35,9 @@
       name = "identity_token_store_lib_test"
       environments = basic_envs
     },
+    {
+      name = "oauth_unittests"
+      environments = basic_envs
+    },
   ]
 }
diff --git a/src/identity/lib/meta/oauth_unittests.cmx b/src/identity/lib/meta/oauth_unittests.cmx
new file mode 100644
index 0000000..30723ca
--- /dev/null
+++ b/src/identity/lib/meta/oauth_unittests.cmx
@@ -0,0 +1,8 @@
+{
+    "program": {
+        "binary": "test/oauth_unittests"
+    },
+    "sandbox": {
+        "services": []
+    }
+}
diff --git a/src/identity/lib/oauth/BUILD.gn b/src/identity/lib/oauth/BUILD.gn
new file mode 100644
index 0000000..76a8b6b
--- /dev/null
+++ b/src/identity/lib/oauth/BUILD.gn
@@ -0,0 +1,52 @@
+# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/package.gni")
+
+source_set("lib") {
+  sources = [
+    "oauth_request_builder.cc",
+    "oauth_request_builder.h",
+    "oauth_response.cc",
+    "oauth_response.h",
+  ]
+
+  public_deps = [
+    "//sdk/fidl/fuchsia.auth",
+    "//sdk/fidl/fuchsia.net.oldhttp",
+  ]
+  deps = [
+    "//garnet/public/lib/fsl",
+    "//garnet/public/lib/network_wrapper",
+    "//peridot/lib/rapidjson",
+    "//src/lib/fxl",
+  ]
+}
+
+executable("oauth_unittests") {
+  testonly = true
+
+  deps = [
+    ":unittests",
+    "//third_party/googletest:gtest_main",
+  ]
+}
+
+source_set("unittests") {
+  testonly = true
+
+  sources = [
+    "oauth_request_builder_unittest.cc",
+    "oauth_response_unittest.cc",
+  ]
+
+  deps = [
+    ":lib",
+    "//garnet/public/lib/fsl",
+    "//peridot/lib/rapidjson",
+    "//src/lib/fxl",
+    "//src/lib/fxl:printers",
+    "//third_party/googletest:gtest",
+  ]
+}
diff --git a/src/identity/lib/oauth/oauth_request_builder.cc b/src/identity/lib/oauth/oauth_request_builder.cc
new file mode 100644
index 0000000..e8e0d9c
--- /dev/null
+++ b/src/identity/lib/oauth/oauth_request_builder.cc
@@ -0,0 +1,122 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/identity/lib/oauth/oauth_request_builder.h"
+
+#include <iomanip>
+#include <iostream>
+
+#include "lib/fsl/socket/strings.h"
+#include "lib/fsl/vmo/strings.h"
+#include "src/lib/fxl/logging.h"
+#include "src/lib/fxl/macros.h"
+#include "src/lib/fxl/strings/join_strings.h"
+#include "src/lib/fxl/strings/string_number_conversions.h"
+
+namespace auth_providers {
+namespace oauth {
+
+namespace http = ::fuchsia::net::oldhttp;
+
+namespace {
+
+std::string UrlEncode(const std::string& value) {
+  std::ostringstream escaped;
+  escaped.fill('0');
+  escaped << std::hex;
+
+  for (char c : value) {
+    // Keep alphanumeric and other accepted characters intact
+    if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '=' ||
+        c == '&' || c == '+') {
+      escaped << c;
+      continue;
+    }
+
+    // Any other characters are percent-encoded
+    escaped << std::uppercase;
+    escaped << '%' << std::setw(2) << int(static_cast<unsigned char>(c));
+    escaped << std::nouppercase;
+  }
+
+  return escaped.str();
+}
+
+}  // namespace
+
+OAuthRequestBuilder::OAuthRequestBuilder(const std::string& url,
+                                         const std::string& method)
+    : url_(url), method_(method) {
+  FXL_CHECK(!url_.empty());
+  FXL_CHECK(!method_.empty());
+}
+
+OAuthRequestBuilder::~OAuthRequestBuilder() {}
+
+OAuthRequestBuilder& OAuthRequestBuilder::SetAuthorizationHeader(
+    const std::string& token) {
+  FXL_DCHECK(!token.empty());
+  http_headers_["Authorization"] = "Bearer " + token;
+  return *this;
+}
+
+OAuthRequestBuilder& OAuthRequestBuilder::SetUrlEncodedBody(
+    const std::string& body) {
+  http_headers_["content-type"] = "application/x-www-form-urlencoded";
+
+  if (body.empty()) {
+    return *this;
+  }
+  return SetRequestBody(UrlEncode(body));
+}
+
+OAuthRequestBuilder& OAuthRequestBuilder::SetJsonBody(const std::string& body) {
+  http_headers_["accept"] = "application/json";
+  http_headers_["content-type"] = "application/json";
+  return SetRequestBody(body);
+}
+
+OAuthRequestBuilder& OAuthRequestBuilder::SetQueryParams(
+    std::map<std::string, std::string> query_params) {
+  for (auto it = query_params.begin(); it != query_params.end(); ++it) {
+    query_string_ += (it == query_params.begin() ? "?" : "&");
+    query_string_ += UrlEncode(it->first) + "=" + UrlEncode(it->second);
+  }
+  return *this;
+}
+
+http::URLRequest OAuthRequestBuilder::Build() const {
+  fsl::SizedVmo data;
+  auto result = fsl::VmoFromString(request_body_, &data);
+  FXL_CHECK(result);
+
+  http::URLRequest request;
+  request.url = url_ + query_string_;
+  request.method = method_;
+  request.auto_follow_redirects = true;
+  request.body = http::URLBody::New();
+  request.body->set_buffer(std::move(data).ToTransport());
+  for (const auto& http_header : http_headers_) {
+    http::HttpHeader hdr;
+    hdr.name = http_header.first;
+    hdr.value = http_header.second;
+    request.headers.push_back(std::move(hdr));
+  }
+
+  return request;
+}
+
+OAuthRequestBuilder& OAuthRequestBuilder::SetRequestBody(
+    const std::string& body) {
+  request_body_ = body;
+
+  uint64_t data_size = request_body_.length();
+  if (data_size > 0)
+    http_headers_["content-length"] = fxl::NumberToString(data_size).data();
+
+  return *this;
+}
+
+}  // namespace oauth
+}  // namespace auth_providers
diff --git a/src/identity/lib/oauth/oauth_request_builder.h b/src/identity/lib/oauth/oauth_request_builder.h
new file mode 100644
index 0000000..44ba3f9
--- /dev/null
+++ b/src/identity/lib/oauth/oauth_request_builder.h
@@ -0,0 +1,59 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_IDENTITY_LIB_OAUTH_OAUTH_REQUEST_BUILDER_H_
+#define SRC_IDENTITY_LIB_OAUTH_OAUTH_REQUEST_BUILDER_H_
+
+#include <fuchsia/net/oldhttp/cpp/fidl.h>
+
+namespace auth_providers {
+namespace oauth {
+
+// Request builder for an OAuth Https Request. This builder converts the
+// oauth endpoint request to an URI in the format as described by the OAuth
+// protocol specification:
+//     - https://tools.ietf.org/html/rfc6749
+class OAuthRequestBuilder {
+ public:
+  OAuthRequestBuilder(const std::string& url, const std::string& method);
+
+  ~OAuthRequestBuilder();
+
+  // Sets the bearer token in the http authorization header field.
+  OAuthRequestBuilder& SetAuthorizationHeader(const std::string& token);
+
+  // Sets the HTTP request body to the url encoded format of |body|. This
+  // method also adds the relevant http request headers for content-type and
+  // content-length fields for posting "application/x-www-form-urlencoded" MIME
+  // datatypes.
+  OAuthRequestBuilder& SetUrlEncodedBody(const std::string& body);
+
+  // Sets the HTTP request body to the json encoded string |body|. This method
+  // also adds the relevant http headers for accept, content-type and
+  // content-length fields for posting JSON data.
+  OAuthRequestBuilder& SetJsonBody(const std::string& body);
+
+  // Url encodes the query params which are appended to the url string while
+  // building the request.
+  OAuthRequestBuilder& SetQueryParams(
+      std::map<std::string, std::string> query_params);
+
+  // Returns an HTTP |URLRequest| handle for the OAuth endpoint.
+  ::fuchsia::net::oldhttp::URLRequest Build() const;
+
+ private:
+  // Sets the HTTP request body field to |body|.
+  OAuthRequestBuilder& SetRequestBody(const std::string& body);
+
+  const std::string url_;
+  const std::string method_;
+  std::string query_string_;
+  std::string request_body_;
+  std::map<std::string, std::string> http_headers_;
+};
+
+}  // namespace oauth
+}  // namespace auth_providers
+
+#endif  // SRC_IDENTITY_LIB_OAUTH_OAUTH_REQUEST_BUILDER_H_
diff --git a/src/identity/lib/oauth/oauth_request_builder_unittest.cc b/src/identity/lib/oauth/oauth_request_builder_unittest.cc
new file mode 100644
index 0000000..6aa3298
--- /dev/null
+++ b/src/identity/lib/oauth/oauth_request_builder_unittest.cc
@@ -0,0 +1,134 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/identity/lib/oauth/oauth_request_builder.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "peridot/lib/rapidjson/rapidjson.h"
+#include "src/lib/fxl/logging.h"
+
+namespace auth_providers {
+namespace oauth {
+
+namespace {
+
+constexpr char kTestUrl[] = "http://example.org";
+constexpr char kPostMethod[] = "POST";
+constexpr char kGetMethod[] = "GET";
+
+class OAuthRequestBuilderTest : public ::testing::Test {
+ protected:
+  OAuthRequestBuilderTest() {}
+
+  ~OAuthRequestBuilderTest() override {}
+};
+
+TEST_F(OAuthRequestBuilderTest, JsonEncodedPostRequest) {
+  rapidjson::Document json_doc;
+  json_doc.Parse(R"({"test_key":"test_val"})");
+
+  // convert json document to string
+  rapidjson::StringBuffer strbuf;
+  strbuf.Clear();
+  rapidjson::Writer<rapidjson::StringBuffer> writer(strbuf);
+  json_doc.Accept(writer);
+
+  auto req = OAuthRequestBuilder(kTestUrl, kPostMethod)
+                 .SetJsonBody(strbuf.GetString())
+                 .Build();
+
+  EXPECT_TRUE(req.url.find("example.org") != std::string::npos);
+  EXPECT_EQ(req.method, kPostMethod);
+  for (const auto& header : *req.headers) {
+    auto hdr_name = header.name;
+    if (hdr_name == "content-type") {
+      EXPECT_EQ(header.value, "application/json");
+    } else if (hdr_name == "content-length") {
+      EXPECT_TRUE(atoi(header.value.c_str()) > 0);
+    }
+  }
+}
+
+TEST_F(OAuthRequestBuilderTest, UrlEncodedPostRequest) {
+  auto req = OAuthRequestBuilder(kTestUrl, kPostMethod)
+                 .SetUrlEncodedBody("test_data")
+                 .Build();
+
+  EXPECT_TRUE(req.url.find("example.org") != std::string::npos);
+  EXPECT_EQ(req.method, kPostMethod);
+  EXPECT_TRUE(req.body);
+  for (const auto& header : *req.headers) {
+    auto hdr_name = header.name;
+    if (hdr_name == "content-type") {
+      EXPECT_EQ(header.value, "application/x-www-form-urlencoded");
+    } else if (hdr_name == "content-length") {
+      EXPECT_TRUE(atoi(header.value.c_str()) > 0);
+    }
+  }
+}
+
+TEST_F(OAuthRequestBuilderTest, EmptyBodyPostRequest) {
+  auto req =
+      OAuthRequestBuilder(kTestUrl, kPostMethod).SetUrlEncodedBody("").Build();
+
+  EXPECT_TRUE(req.url.find("example.org") != std::string::npos);
+  EXPECT_EQ(req.method, kPostMethod);
+  for (const auto& header : *req.headers) {
+    auto hdr_name = header.name;
+    if (hdr_name == "content-type") {
+      EXPECT_EQ(header.value, "application/x-www-form-urlencoded");
+    } else if (hdr_name == "content-length") {
+      EXPECT_TRUE(atoi(header.value.c_str()) == 0);
+    } else {
+      ASSERT_TRUE("This header should never been set");
+    }
+  }
+}
+
+TEST_F(OAuthRequestBuilderTest, CheckAuthHeader) {
+  auto req = OAuthRequestBuilder(kTestUrl, kPostMethod)
+                 .SetUrlEncodedBody("test_data")
+                 .SetAuthorizationHeader("test_token")
+                 .Build();
+
+  EXPECT_TRUE(req.url.find("example.org") != std::string::npos);
+  EXPECT_EQ(req.method, kPostMethod);
+  EXPECT_TRUE(req.body);
+  for (const auto& header : *req.headers) {
+    auto hdr_name = header.name;
+    if (hdr_name == "Authorization") {
+      EXPECT_TRUE(header.value.find("test_token") != std::string::npos);
+    }
+  }
+}
+
+TEST_F(OAuthRequestBuilderTest, GetRequest) {
+  auto req = OAuthRequestBuilder(kTestUrl, kGetMethod).Build();
+
+  EXPECT_TRUE(req.url.find("example.org") != std::string::npos);
+  EXPECT_EQ(req.method, kGetMethod);
+}
+
+TEST_F(OAuthRequestBuilderTest, GetRequestWithQueryParams) {
+  std::map<std::string, std::string> params;
+  params["foo1"] = "bar1";
+  params["foo2"] = "bar2";
+  params["foo3"] = "bar 3";
+  auto req =
+      OAuthRequestBuilder(kTestUrl, kGetMethod).SetQueryParams(params).Build();
+
+  EXPECT_TRUE(req.url.find("example.org") != std::string::npos);
+  EXPECT_TRUE(req.url.find("foo1") != std::string::npos);
+  EXPECT_TRUE(req.url.find("foo2") != std::string::npos);
+  // check if the param values are url encoded
+  EXPECT_TRUE(req.url.find("bar%203") != std::string::npos);
+  EXPECT_EQ(req.method, kGetMethod);
+}
+
+}  // namespace
+
+}  // namespace oauth
+}  // namespace auth_providers
diff --git a/src/identity/lib/oauth/oauth_response.cc b/src/identity/lib/oauth/oauth_response.cc
new file mode 100644
index 0000000..f758e0a
--- /dev/null
+++ b/src/identity/lib/oauth/oauth_response.cc
@@ -0,0 +1,76 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/identity/lib/oauth/oauth_response.h"
+
+#include "lib/fsl/socket/strings.h"
+#include "rapidjson/error/en.h"
+#include "src/lib/fxl/logging.h"
+#include "src/lib/fxl/macros.h"
+
+namespace auth_providers {
+namespace oauth {
+
+namespace http = ::fuchsia::net::oldhttp;
+
+using fuchsia::auth::AuthProviderStatus;
+
+OAuthResponse ParseOAuthResponse(http::URLResponse response) {
+  rapidjson::Document out;
+  if (response.error) {
+    FXL_LOG(ERROR) << "Encountered error: " +
+                          std::to_string(response.error->code) +
+                          " ,with description: " +
+                          response.error->description->data();
+    return OAuthResponse(AuthProviderStatus::NETWORK_ERROR,
+                         response.error->description->data(), std::move(out));
+  }
+
+  std::string response_body;
+  if (response.body) {
+    FXL_DCHECK(response.body->is_stream());
+    if (!fsl::BlockingCopyToString(std::move(response.body->stream()),
+                                   &response_body)) {
+      FXL_LOG(ERROR) << "Internal error while reading response from socket,"
+                        "network returned: " +
+                            std::to_string(response.status_code);
+      return OAuthResponse(AuthProviderStatus::NETWORK_ERROR,
+                           "Error reading response from socket",
+                           std::move(out));
+    }
+  }
+
+  // OAuth errors are sent in the response body, parse the json response first
+  // to introspect the response.
+  rapidjson::ParseResult ok = out.Parse(response_body);
+  if (!ok) {
+    std::string error_msg = GetParseError_En(ok.Code());
+    return OAuthResponse(
+        AuthProviderStatus::BAD_RESPONSE,
+        "Error in parsing json response[" + response_body + "]: " + error_msg,
+        std::move(out));
+  }
+  switch (response.status_code) {
+    case 200:  // Success
+      return OAuthResponse(AuthProviderStatus::OK, "", std::move(out));
+    case 400:  // Bad request errors
+    case 401:  // Unauthorized, returned with invalid_client.
+    case 403:  // Forbidden, user denied access.
+    default:
+      std::string oauth_error(out.HasMember("error") && out["error"].IsString()
+                                  ? out["error"].GetString()
+                                  : "");
+      auto status = (oauth_error == "invalid_grant")
+                        ? AuthProviderStatus::REAUTH_REQUIRED
+                        : AuthProviderStatus::OAUTH_SERVER_ERROR;
+
+      return OAuthResponse(status,
+                           "OAuth backend returned error: " +
+                               std::to_string(response.status_code),
+                           std::move(out));
+  }
+}
+
+}  // namespace oauth
+}  // namespace auth_providers
diff --git a/src/identity/lib/oauth/oauth_response.h b/src/identity/lib/oauth/oauth_response.h
new file mode 100644
index 0000000..26ccf5e
--- /dev/null
+++ b/src/identity/lib/oauth/oauth_response.h
@@ -0,0 +1,34 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_IDENTITY_LIB_OAUTH_OAUTH_RESPONSE_H_
+#define SRC_IDENTITY_LIB_OAUTH_OAUTH_RESPONSE_H_
+
+#include <fuchsia/auth/cpp/fidl.h>
+#include <fuchsia/net/oldhttp/cpp/fidl.h>
+
+#include "rapidjson/document.h"
+
+namespace auth_providers {
+namespace oauth {
+
+struct OAuthResponse {
+  const fuchsia::auth::AuthProviderStatus status;
+  const std::string error_description;
+  rapidjson::Document json_response;
+
+  OAuthResponse(const fuchsia::auth::AuthProviderStatus& status,
+                const std::string& error_description,
+                rapidjson::Document json_response)
+      : status(status),
+        error_description(error_description),
+        json_response(std::move(json_response)) {}
+};
+
+OAuthResponse ParseOAuthResponse(::fuchsia::net::oldhttp::URLResponse response);
+
+}  // namespace oauth
+}  // namespace auth_providers
+
+#endif  // SRC_IDENTITY_LIB_OAUTH_OAUTH_RESPONSE_H_
diff --git a/src/identity/lib/oauth/oauth_response_unittest.cc b/src/identity/lib/oauth/oauth_response_unittest.cc
new file mode 100644
index 0000000..7f1c4fb
--- /dev/null
+++ b/src/identity/lib/oauth/oauth_response_unittest.cc
@@ -0,0 +1,131 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/identity/lib/oauth/oauth_response.h"
+
+#include <stdlib.h>
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "lib/fsl/socket/strings.h"
+#include "peridot/lib/rapidjson/rapidjson.h"
+#include "src/lib/fxl/strings/string_number_conversions.h"
+
+namespace auth_providers {
+namespace oauth {
+
+using fuchsia::auth::AuthProviderStatus;
+
+namespace {
+
+namespace http = ::fuchsia::net::oldhttp;
+
+http::URLResponse FakeError(int32_t code, std::string reason) {
+  http::URLResponse response;
+  response.error = http::HttpError::New();
+  response.error->code = code;
+  response.error->description = reason;
+  return response;
+}
+
+http::URLResponse FakeSuccess(int32_t code, const std::string& body) {
+  http::URLResponse response;
+  response.body = http::URLBody::New();
+  response.body->set_stream(fsl::WriteStringToSocket(body));
+  response.status_code = code;
+
+  http::HttpHeader content_length_header;
+  content_length_header.name = "content-length";
+  content_length_header.value = fxl::NumberToString(body.size());
+  response.headers.push_back(std::move(content_length_header));
+
+  return response;
+}
+
+void VerifyOAuthResponse(OAuthResponse got, OAuthResponse want) {
+  EXPECT_EQ(want.status, got.status);
+  EXPECT_TRUE(got.error_description.find(want.error_description) !=
+              std::string::npos);
+
+  if (want.json_response.IsObject() && got.json_response.IsObject()) {
+    for (auto& m : want.json_response.GetObject()) {
+      auto key = m.name.GetString();
+      rapidjson::Value::ConstMemberIterator itr =
+          got.json_response.FindMember(key);
+      EXPECT_TRUE(itr != got.json_response.MemberEnd());
+
+      if (m.value.IsString())
+        EXPECT_EQ(got.json_response[key].GetString(), itr->value.GetString());
+      if (m.value.IsUint64())
+        EXPECT_EQ(got.json_response[key].GetUint64(), itr->value.GetUint64());
+      if (m.value.IsInt())
+        EXPECT_EQ(got.json_response[key].GetInt(), itr->value.GetInt());
+      if (m.value.IsInt64())
+        EXPECT_EQ(got.json_response[key].GetInt64(), itr->value.GetInt64());
+      if (m.value.IsBool())
+        EXPECT_EQ(got.json_response[key].GetBool(), itr->value.GetBool());
+      if (m.value.IsArray()) {
+        for (uint i = 0; i < want.json_response[key].GetArray().Size(); i++) {
+          EXPECT_EQ(want.json_response[key].GetArray()[i],
+                    got.json_response[key].GetArray()[i]);
+        }
+      }
+    }
+  }
+}
+
+}  // namespace
+
+class OAuthResponseTest : public ::testing::Test {
+ protected:
+  OAuthResponseTest() {}
+
+  ~OAuthResponseTest() override {}
+};
+
+TEST_F(OAuthResponseTest, CheckParseOAuthResponse) {
+  rapidjson::Document no_json_body;
+  VerifyOAuthResponse(ParseOAuthResponse(FakeError(-2, "Bad request")),
+                      OAuthResponse(AuthProviderStatus::NETWORK_ERROR,
+                                    "Bad request", std::move(no_json_body)));
+
+  rapidjson::Document json_200;
+  json_200.Parse("{\"token\":\"xyz\"}");
+  VerifyOAuthResponse(
+      ParseOAuthResponse(
+          FakeSuccess(200, modular::JsonValueToPrettyString(json_200))),
+      OAuthResponse(AuthProviderStatus::OK, "", std::move(json_200)));
+
+  rapidjson::Document json_400;
+  json_400.Parse("{\"error\":\"invalid_grant\"}");
+  VerifyOAuthResponse(ParseOAuthResponse(FakeSuccess(
+                          400, modular::JsonValueToPrettyString(json_400))),
+                      OAuthResponse(AuthProviderStatus::REAUTH_REQUIRED, "400",
+                                    std::move(json_400)));
+
+  rapidjson::Document json_400_br;
+  json_400_br.Parse("{\"error\":\"invalid_argument\"}");
+  VerifyOAuthResponse(ParseOAuthResponse(FakeSuccess(
+                          400, modular::JsonValueToPrettyString(json_400_br))),
+                      OAuthResponse(AuthProviderStatus::OAUTH_SERVER_ERROR,
+                                    "400", std::move(json_400_br)));
+
+  rapidjson::Document json_401;
+  json_401.Parse("{\"error\":\"invalid_client\"}");
+  VerifyOAuthResponse(ParseOAuthResponse(FakeSuccess(
+                          401, modular::JsonValueToPrettyString(json_401))),
+                      OAuthResponse(AuthProviderStatus::OAUTH_SERVER_ERROR,
+                                    "401", std::move(json_401)));
+
+  rapidjson::Document json_403;
+  json_403.Parse("{\"error\":\"access_denied\"}");
+  VerifyOAuthResponse(ParseOAuthResponse(FakeSuccess(
+                          403, modular::JsonValueToPrettyString(json_403))),
+                      OAuthResponse(AuthProviderStatus::OAUTH_SERVER_ERROR,
+                                    "403", std::move(json_403)));
+}
+
+}  // namespace oauth
+}  // namespace auth_providers