diff --git a/Gemfile b/Gemfile
index 7b151dc5f..a7a40d797 100644
--- a/Gemfile
+++ b/Gemfile
@@ -16,6 +16,11 @@ group :development do
gem 'launchy', '~> 2.4'
gem 'dotenv', '~> 2.0'
gem 'fakefs', '~> 0.6', require: "fakefs/safe"
+ gem 'google-id-token', '~> 1.3'
+ gem 'os', '~> 0.9'
+ gem 'rmail', '~> 1.1'
+ gem 'sinatra', '~> 1.4'
+ gem 'redis', '~> 3.2'
end
platforms :jruby do
diff --git a/samples/Gemfile b/samples/cli/Gemfile
similarity index 73%
rename from samples/Gemfile
rename to samples/cli/Gemfile
index 828cb840a..25ee33dbf 100644
--- a/samples/Gemfile
+++ b/samples/cli/Gemfile
@@ -1,6 +1,6 @@
source 'https://rubygems.org'
-gem 'google-api-client', '~> 0.9.pre4'
+gem 'google-api-client', '~> 0.9'
gem 'thor', '~> 0.19'
gem 'os', '~> 0.9'
gem 'rmail', '~> 1.1'
diff --git a/samples/README.md b/samples/cli/README.md
similarity index 100%
rename from samples/README.md
rename to samples/cli/README.md
diff --git a/samples/google-api-samples b/samples/cli/google-api-samples
similarity index 100%
rename from samples/google-api-samples
rename to samples/cli/google-api-samples
diff --git a/samples/lib/base_cli.rb b/samples/cli/lib/base_cli.rb
similarity index 100%
rename from samples/lib/base_cli.rb
rename to samples/cli/lib/base_cli.rb
diff --git a/samples/lib/samples/analytics.rb b/samples/cli/lib/samples/analytics.rb
similarity index 100%
rename from samples/lib/samples/analytics.rb
rename to samples/cli/lib/samples/analytics.rb
diff --git a/samples/lib/samples/calendar.rb b/samples/cli/lib/samples/calendar.rb
similarity index 100%
rename from samples/lib/samples/calendar.rb
rename to samples/cli/lib/samples/calendar.rb
diff --git a/samples/lib/samples/drive.rb b/samples/cli/lib/samples/drive.rb
similarity index 100%
rename from samples/lib/samples/drive.rb
rename to samples/cli/lib/samples/drive.rb
diff --git a/samples/lib/samples/gmail.rb b/samples/cli/lib/samples/gmail.rb
similarity index 100%
rename from samples/lib/samples/gmail.rb
rename to samples/cli/lib/samples/gmail.rb
diff --git a/samples/lib/samples/pubsub.rb b/samples/cli/lib/samples/pubsub.rb
similarity index 100%
rename from samples/lib/samples/pubsub.rb
rename to samples/cli/lib/samples/pubsub.rb
diff --git a/samples/lib/samples/translate.rb b/samples/cli/lib/samples/translate.rb
similarity index 100%
rename from samples/lib/samples/translate.rb
rename to samples/cli/lib/samples/translate.rb
diff --git a/samples/lib/samples/you_tube.rb b/samples/cli/lib/samples/you_tube.rb
similarity index 100%
rename from samples/lib/samples/you_tube.rb
rename to samples/cli/lib/samples/you_tube.rb
diff --git a/samples/web/Gemfile b/samples/web/Gemfile
new file mode 100644
index 000000000..9c86e389f
--- /dev/null
+++ b/samples/web/Gemfile
@@ -0,0 +1,7 @@
+source 'https://rubygems.org'
+
+gem 'google-api-client', '~> 0.9'
+gem 'google-id-token', '~> 1.3'
+gem 'sinatra', '~> 1.4'
+gem 'redis', '~> 3.2'
+gem 'dotenv'
diff --git a/samples/web/README.md b/samples/web/README.md
new file mode 100644
index 000000000..b8c4a10dd
--- /dev/null
+++ b/samples/web/README.md
@@ -0,0 +1,44 @@
+# API Samples
+
+This directory contains a simple Sinatra web app illustrating how to use the client
+in a server-side web environment.
+
+It illustrates a few key concepts:
+
+* Using [Google Sign-in](https://developers.google.com/identity) for authentication.
+* Using the [googleauth gem](https://github.com/google/google-auth-library-ruby) to
+ request incremental authorization as more permissions are needed.
+
+# Setup
+
+* Create a project at https://console.developers.google.com
+* Go to the `API Manager` and enable the `Drive` and `Calendar` APIs
+* Go to `Credentials` and create a new OAuth Client ID of type 'Web application'
+ * Use `http://localhost:4567/oauth2callback` as the redirect URL
+ * Use `http://localhost:4567` as the JavaScript origin
+
+Additional details on how to enable APIs and create credentials can be
+found in the help guide in the console.
+
+## Example Environment Settings
+
+For convenience, application credentials can be read from the shell environment
+or placed in a .env file.
+
+After setup, your .env file might look something like:
+
+```
+GOOGLE_CLIENT_ID=479164972499-i7j6av7bp2s4on5ltb7pjXXXXXXXXXX.apps.googleusercontent.com
+GOOGLE_CLIENT_SECRET=JBotCTG5biFWGzXXXXXXXXXX
+```
+
+# Running the samples
+
+To start the server, run
+
+```
+ruby app.rb
+```
+
+Open `http://localhost:4567/` in your browser to explore the sample.
+
diff --git a/samples/web/app.rb b/samples/web/app.rb
new file mode 100644
index 000000000..a19ce278d
--- /dev/null
+++ b/samples/web/app.rb
@@ -0,0 +1,120 @@
+# Copyright 2015 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'sinatra'
+require 'googleauth'
+require 'googleauth/stores/redis_token_store'
+require 'google/apis/drive_v3'
+require 'google/apis/calendar_v3'
+require 'google-id-token'
+require 'dotenv'
+
+LOGIN_URL = '/'
+
+configure do
+ Dotenv.load
+
+ Google::Apis::ClientOptions.default.application_name = 'Ruby client samples'
+ Google::Apis::ClientOptions.default.application_version = '0.9'
+ Google::Apis::RequestOptions.default.retries = 3
+
+ enable :sessions
+ set :show_exceptions, false
+ set :client_id, Google::Auth::ClientId.new(ENV['GOOGLE_CLIENT_ID'],
+ ENV['GOOGLE_CLIENT_SECRET'])
+ set :token_store, Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new)
+end
+
+helpers do
+ # Returns credentials authorized for the requested scopes. If no credentials are available,
+ # redirects the user to authorize access.
+ def credentials_for(scope)
+ authorizer = Google::Auth::WebUserAuthorizer.new(settings.client_id, scope, settings.token_store)
+ user_id = session[:user_id]
+ redirect LOGIN_URL if user_id.nil?
+ credentials = authorizer.get_credentials(user_id, request)
+ if credentials.nil?
+ redirect authorizer.get_authorization_url(login_hint: user_id, request: request)
+ end
+ credentials
+ end
+
+ def resize(url, width)
+ url.sub(/s220/, sprintf('s%d', width))
+ end
+end
+
+# Home page
+get('/') do
+ @client_id = settings.client_id.id
+ erb :home
+end
+
+# Log in the user by validating the identity token generated by the Google Sign-In button.
+# This checks that the token is signed by Google, current, and is intended for this application.
+#
+post('/signin') do
+ audience = settings.client_id.id
+ # Important: The google-id-token gem is not production ready. If using, consider fetching and
+ # supplying the valid keys separately rather than using the built-in certificate fetcher.
+ validator = GoogleIDToken::Validator.new
+ claim = validator.check(params['id_token'], audience, audience)
+ if claim
+ session[:user_id] = claim['sub']
+ session[:user_email] = claim['email']
+ 200
+ else
+ logger.info('No valid identity token present')
+ 401
+ end
+end
+
+# Retrieve the 10 most recently modified files in Google Drive
+get('/drive') do
+ drive = Google::Apis::DriveV3::DriveService.new
+ drive.authorization = credentials_for(Google::Apis::DriveV3::AUTH_DRIVE)
+ @result = drive.list_files(page_size: 10,
+ fields: 'files(name,modified_time,web_view_link),next_page_token')
+ erb :drive
+end
+
+# Retrieve the next 10 upcoming events from Google Calendar
+get('/calendar') do
+ calendar = Google::Apis::CalendarV3::CalendarService.new
+ calendar.authorization = credentials_for(Google::Apis::CalendarV3::AUTH_CALENDAR)
+ calendar_id = 'primary'
+ @result = calendar.list_events(calendar_id,
+ max_results: 10,
+ single_events: true,
+ order_by: 'startTime',
+ time_min: Time.now.iso8601)
+ erb :calendar
+end
+
+
+# Callback for authorization requests. This saves the autorization code and
+# redirects back to the URL that originally requested authorization. The code is
+# redeemed on the next request.
+#
+# Important: While the deferred approach is generally easier, it doesn't play well
+# with developer mode and sinatra's default cookie-based session implementation. Changes to the
+# session state are lost if the page doesn't render due to error, which can lead to further
+# errors indicating the code has already been redeemed.
+#
+# Disabling show_exceptions or using a different session provider (E.g. Rack::Session::Memcache)
+# avoids the issue.
+get('/oauth2callback') do
+ target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request)
+ redirect target_url
+end
diff --git a/samples/web/views/calendar.erb b/samples/web/views/calendar.erb
new file mode 100644
index 000000000..97ecc4696
--- /dev/null
+++ b/samples/web/views/calendar.erb
@@ -0,0 +1,34 @@
+<%#
+# Copyright 2016 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+%>
+
+ Next 10 events
+
+
+
+ Time |
+ Summary |
+
+
+
+ <% @result.items.each do |event| %>
+
+ <%= event.start.date_time || event.start.date %> |
+ <%= event.summary %> |
+
+ <% end %>
+
+
+
diff --git a/samples/web/views/drive.erb b/samples/web/views/drive.erb
new file mode 100644
index 000000000..28fce3361
--- /dev/null
+++ b/samples/web/views/drive.erb
@@ -0,0 +1,33 @@
+<%#
+# Copyright 2016 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+%>
+
+
10 most recently modified files
+
+
+
+ Name |
+ Last Modified |
+
+
+
+ <% @result.files.each do |file| %>
+
+ <%= file.name %> |
+ <%= file.modified_time %> |
+
+ <% end %>
+
+
diff --git a/samples/web/views/home.erb b/samples/web/views/home.erb
new file mode 100644
index 000000000..afd9a4e6a
--- /dev/null
+++ b/samples/web/views/home.erb
@@ -0,0 +1,35 @@
+<%#
+# Copyright 2016 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+%>
+
+
diff --git a/samples/web/views/layout.erb b/samples/web/views/layout.erb
new file mode 100644
index 000000000..732860c76
--- /dev/null
+++ b/samples/web/views/layout.erb
@@ -0,0 +1,40 @@
+<%#
+# Copyright 2016 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+%>
+
+
+
+
+
+
+
+
+
+
+ <%= yield %>
+
+
+