Keywords: Google OAuth 2.0 | invalid_request | local redirect
Abstract: This article provides an in-depth analysis of the Error 400: invalid_request in Google OAuth 2.0, particularly when using urn:ietf:wg:oauth:2.0:oob as the redirect URI. Based on official documentation and best practices, it explains Google's policy changes to phase out the OOB flow for enhanced security and offers a complete solution for migrating to local redirects. Through detailed technical analysis and code examples, it helps developers understand OAuth 2.0 security mechanisms and implement safer authentication flows.
Problem Background and Symptoms
In Google OAuth 2.0 implementations, developers often encounter the Error 400: invalid_request, specifically: "You can't sign in to this app because it doesn't comply with Google's OAuth 2.0 policy for keeping apps secure." This issue is particularly prominent when using urn:ietf:wg:oauth:2.0:oob as the redirect_uri. The OOB flow was originally designed for command-line or headless environments, allowing users to complete authentication by copying and pasting an authorization code.
Root Cause Analysis
According to the official response from the Google OAuth team, this issue stems from security policy updates. Google announced Making Google OAuth interactions safer by using more secure OAuth flows in February 2022, aiming to phase out the OOB flow due to potential security risks. Newly created client IDs default to stricter security standards, while older client IDs may still be compatible with the old flow, explaining why different client IDs in the same project behave inconsistently.
Solution: Migrating to Local Redirects
The official recommendation is to use local redirects as an alternative. For command-line scripts, this can be achieved by implementing a simple HTTP server to receive the redirect. Below is a Ruby-based example code demonstrating the migration from OOB to local redirects:
require 'sinatra/base'
require 'thin'
# Set up a local server to receive authorization codes
def run_local_server(authorizer, port, user_id)
Thread.new {
Thread.current[:server] = Sinatra.new do
set :port, port
set :server, %w[ thin ]
get "/" do
code = params["code"]
if code.nil?
halt 400, "Authorization code missing"
end
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id,
code: code
)
"Authentication successful. You can close this window."
end
end
Thread.current[:server].run!
}
end
# Updated function to get user credentials
def user_credentials_for(scope, user_id = 'default')
client_id = Google::Auth::ClientId.new(ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'])
token_store = Google::Auth::Stores::FileTokenStore.new(file: ENV['GOOGLE_CREDENTIAL_STORE'])
port = 8080
redirect_uri = "http://localhost:#{port}/"
authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store, redirect_uri)
credentials = authorizer.get_credentials(user_id)
if credentials.nil?
server_thread = run_local_server(authorizer, port, user_id)
url = authorizer.get_authorization_url
puts "Open this URL in your browser: #{url}"
# Wait for user to complete authorization or manually input code
input = gets.chomp
server_thread[:server].stop!
server_thread.join
# Process input, which could be a full URL or plain code
if input.include?("http")
require 'uri'
code = URI.parse(input).query.split("&").find { |param| param.start_with?("code=") }.split("=")[1]
else
code = input
end
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id,
code: code
)
end
credentials
endThis code implements a local HTTP server that listens on a specified port to receive redirects from the browser. If the browser cannot access the local server, users can still manually input the authorization code from the failed URL. This approach meets security requirements while maintaining usability in command-line environments.
Alternative Solutions
In addition to local redirects, Google offers the device flow as an alternative for headless environments, suitable for non-sensitive scopes. The device flow allows users to complete authorization on another device, making it ideal for completely browser-less scenarios. Developers should choose the appropriate solution based on specific needs.
Implementation Recommendations and Best Practices
During migration, it is advisable to gradually update all client IDs to use local redirects. Ensure that redirect URIs are correctly configured in the Google Cloud Console, such as http://localhost:8080/. For internal projects, enabling "Trust internal, domain-owned apps" can simplify domain authentication. Regularly review Google OAuth policy updates to maintain compliance.
By understanding the security evolution of OAuth 2.0 and proactively adopting safer flows, developers can avoid common authentication errors and enhance the overall security of their applications.