-
Notifications
You must be signed in to change notification settings - Fork 184
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7b525f7
commit 268f05c
Showing
7 changed files
with
507 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio 14 | ||
VisualStudioVersion = 14.0.25123.0 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OAuthConsoleApp", "OAuthConsoleApp\OAuthConsoleApp.csproj", "{AC38CFAC-5563-454E-B7E5-FA2946FD8D13}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{AC38CFAC-5563-454E-B7E5-FA2946FD8D13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{AC38CFAC-5563-454E-B7E5-FA2946FD8D13}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{AC38CFAC-5563-454E-B7E5-FA2946FD8D13}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{AC38CFAC-5563-454E-B7E5-FA2946FD8D13}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<configuration> | ||
<startup> | ||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> | ||
</startup> | ||
</configuration> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> | ||
<PropertyGroup> | ||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | ||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | ||
<ProjectGuid>{AC38CFAC-5563-454E-B7E5-FA2946FD8D13}</ProjectGuid> | ||
<OutputType>Exe</OutputType> | ||
<AppDesignerFolder>Properties</AppDesignerFolder> | ||
<RootNamespace>OAuthConsoleApp</RootNamespace> | ||
<AssemblyName>OAuthConsoleApp</AssemblyName> | ||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> | ||
<FileAlignment>512</FileAlignment> | ||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> | ||
</PropertyGroup> | ||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | ||
<PlatformTarget>AnyCPU</PlatformTarget> | ||
<DebugSymbols>true</DebugSymbols> | ||
<DebugType>full</DebugType> | ||
<Optimize>false</Optimize> | ||
<OutputPath>bin\Debug\</OutputPath> | ||
<DefineConstants>DEBUG;TRACE</DefineConstants> | ||
<ErrorReport>prompt</ErrorReport> | ||
<WarningLevel>4</WarningLevel> | ||
</PropertyGroup> | ||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | ||
<PlatformTarget>AnyCPU</PlatformTarget> | ||
<DebugType>pdbonly</DebugType> | ||
<Optimize>true</Optimize> | ||
<OutputPath>bin\Release\</OutputPath> | ||
<DefineConstants>TRACE</DefineConstants> | ||
<ErrorReport>prompt</ErrorReport> | ||
<WarningLevel>4</WarningLevel> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> | ||
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> | ||
<Private>True</Private> | ||
</Reference> | ||
<Reference Include="System" /> | ||
<Reference Include="System.Core" /> | ||
<Reference Include="System.Xml.Linq" /> | ||
<Reference Include="System.Data.DataSetExtensions" /> | ||
<Reference Include="Microsoft.CSharp" /> | ||
<Reference Include="System.Data" /> | ||
<Reference Include="System.Net.Http" /> | ||
<Reference Include="System.Xml" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<Compile Include="Program.cs" /> | ||
<Compile Include="Properties\AssemblyInfo.cs" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<None Include="App.config" /> | ||
<None Include="packages.config" /> | ||
</ItemGroup> | ||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | ||
Other similar extension points exist, see Microsoft.Common.targets. | ||
<Target Name="BeforeBuild"> | ||
</Target> | ||
<Target Name="AfterBuild"> | ||
</Target> | ||
--> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,296 @@ | ||
// 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. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using Newtonsoft.Json; | ||
using System.IO; | ||
using System.Net; | ||
using System.Net.Sockets; | ||
using System.Security.Cryptography; | ||
using System.Runtime.InteropServices; | ||
|
||
namespace OAuthConsoleApp | ||
{ | ||
class Program | ||
{ | ||
static void Main(string[] args) | ||
{ | ||
Console.WriteLine("+-----------------------+"); | ||
Console.WriteLine("| Sign in with Google |"); | ||
Console.WriteLine("+-----------------------+"); | ||
Console.WriteLine(""); | ||
Console.WriteLine("Press any key to sign in..."); | ||
Console.ReadKey(); | ||
|
||
Program p = new Program(); | ||
p.doOAuth(); | ||
|
||
Console.ReadKey(); | ||
} | ||
|
||
// client configuration | ||
const string clientID = "581786658708-elflankerquo1a6vsckabbhn25hclla0.apps.googleusercontent.com"; | ||
const string clientSecret = "3f6NggMbPtrmIBpgx-MK2xXK"; | ||
const string authorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth"; | ||
const string tokenEndpoint = "https://www.googleapis.com/oauth2/v4/token"; | ||
const string userInfoEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo"; | ||
|
||
// ref http://stackoverflow.com/a/3978040 | ||
public static int GetRandomUnusedPort() | ||
{ | ||
var listener = new TcpListener(IPAddress.Loopback, 0); | ||
listener.Start(); | ||
var port = ((IPEndPoint)listener.LocalEndpoint).Port; | ||
listener.Stop(); | ||
return port; | ||
} | ||
|
||
private async void doOAuth() | ||
{ | ||
// Generates state and PKCE values. | ||
string state = randomDataBase64url(32); | ||
string code_verifier = randomDataBase64url(32); | ||
string code_challenge = base64urlencodeNoPadding(sha256(code_verifier)); | ||
const string code_challenge_method = "S256"; | ||
|
||
// Creates a redirect URI using an available port on the loopback address. | ||
string redirectURI = string.Format("http://{0}:{1}/", IPAddress.Loopback, GetRandomUnusedPort()); | ||
output("redirect URI: " + redirectURI); | ||
|
||
// Creates an HttpListener to listen for requests on that redirect URI. | ||
var http = new HttpListener(); | ||
http.Prefixes.Add(redirectURI); | ||
output("Listening.."); | ||
http.Start(); | ||
|
||
// Creates the OAuth 2.0 authorization request. | ||
string authorizationRequest = string.Format("{0}?response_type=code&scope=openid%20profile&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}", | ||
authorizationEndpoint, | ||
System.Uri.EscapeDataString(redirectURI), | ||
clientID, | ||
state, | ||
code_challenge, | ||
code_challenge_method); | ||
|
||
// Opens request in the browser. | ||
System.Diagnostics.Process.Start(authorizationRequest); | ||
|
||
// Waits for the OAuth authorization response. | ||
var context = await http.GetContextAsync(); | ||
|
||
// Brings the Console to Focus. | ||
BringConsoleToFront(); | ||
|
||
// Sends an HTTP response to the browser. | ||
var response = context.Response; | ||
string responseString = string.Format("<html><head><meta http-equiv='refresh' content='10;url=https://google.com'></head><body>Please return to the app.</body></html>"); | ||
var buffer = System.Text.Encoding.UTF8.GetBytes(responseString); | ||
response.ContentLength64 = buffer.Length; | ||
var responseOutput = response.OutputStream; | ||
Task responseTask = responseOutput.WriteAsync(buffer, 0, buffer.Length).ContinueWith((task) => | ||
{ | ||
responseOutput.Close(); | ||
http.Stop(); | ||
Console.WriteLine("HTTP server stopped."); | ||
}); | ||
|
||
// Checks for errors. | ||
if (context.Request.QueryString.Get("error") != null) | ||
{ | ||
output(String.Format("OAuth authorization error: {0}.", context.Request.QueryString.Get("error"))); | ||
return; | ||
} | ||
if (context.Request.QueryString.Get("code") == null | ||
|| context.Request.QueryString.Get("state") == null) | ||
{ | ||
output("Malformed authorization response. " + context.Request.QueryString); | ||
return; | ||
} | ||
|
||
// extracts the code | ||
var code = context.Request.QueryString.Get("code"); | ||
var incoming_state = context.Request.QueryString.Get("state"); | ||
|
||
// Compares the receieved state to the expected value, to ensure that | ||
// this app made the request which resulted in authorization. | ||
if (incoming_state != state) | ||
{ | ||
output(String.Format("Received request with invalid state ({0})", incoming_state)); | ||
return; | ||
} | ||
output("Authorization code: " + code); | ||
|
||
// Starts the code exchange at the Token Endpoint. | ||
performCodeExchange(code, code_verifier, redirectURI); | ||
} | ||
|
||
async void performCodeExchange(string code, string code_verifier, string redirectURI) | ||
{ | ||
output("Exchanging code for tokens..."); | ||
|
||
// builds the request | ||
string tokenRequestURI = "https://www.googleapis.com/oauth2/v4/token"; | ||
string tokenRequestBody = string.Format("code={0}&redirect_uri={1}&client_id={2}&code_verifier={3}&client_secret={4}&scope=&grant_type=authorization_code", | ||
code, | ||
System.Uri.EscapeDataString(redirectURI), | ||
clientID, | ||
code_verifier, | ||
clientSecret | ||
); | ||
|
||
// sends the request | ||
HttpWebRequest tokenRequest = (HttpWebRequest)WebRequest.Create(tokenRequestURI); | ||
tokenRequest.Method = "POST"; | ||
tokenRequest.ContentType = "application/x-www-form-urlencoded"; | ||
tokenRequest.Accept = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; | ||
byte[] _byteVersion = Encoding.ASCII.GetBytes(tokenRequestBody); | ||
tokenRequest.ContentLength = _byteVersion.Length; | ||
Stream stream = tokenRequest.GetRequestStream(); | ||
await stream.WriteAsync(_byteVersion, 0, _byteVersion.Length); | ||
stream.Close(); | ||
|
||
try | ||
{ | ||
// gets the response | ||
WebResponse tokenResponse = await tokenRequest.GetResponseAsync(); | ||
using (StreamReader reader = new StreamReader(tokenResponse.GetResponseStream())) | ||
{ | ||
// reads response body | ||
string responseText = await reader.ReadToEndAsync(); | ||
Console.WriteLine(responseText); | ||
|
||
// converts to dictionary | ||
Dictionary<string, string> tokenEndpointDecoded = JsonConvert.DeserializeObject<Dictionary<string, string>>(responseText); | ||
|
||
string access_token = tokenEndpointDecoded["access_token"]; | ||
userinfoCall(access_token); | ||
} | ||
} | ||
catch (WebException ex) | ||
{ | ||
if (ex.Status == WebExceptionStatus.ProtocolError) | ||
{ | ||
var response = ex.Response as HttpWebResponse; | ||
if (response != null) | ||
{ | ||
output("HTTP: " + response.StatusCode); | ||
using (StreamReader reader = new StreamReader(response.GetResponseStream())) | ||
{ | ||
// reads response body | ||
string responseText = await reader.ReadToEndAsync(); | ||
output(responseText); | ||
} | ||
} | ||
|
||
} | ||
} | ||
} | ||
|
||
|
||
async void userinfoCall(string access_token) | ||
{ | ||
output("Making API Call to Userinfo..."); | ||
|
||
// builds the request | ||
string userinfoRequestURI = "https://www.googleapis.com/oauth2/v3/userinfo"; | ||
|
||
// sends the request | ||
HttpWebRequest userinfoRequest = (HttpWebRequest)WebRequest.Create(userinfoRequestURI); | ||
userinfoRequest.Method = "GET"; | ||
userinfoRequest.Headers.Add(string.Format("Authorization: Bearer {0}", access_token)); | ||
userinfoRequest.ContentType = "application/x-www-form-urlencoded"; | ||
userinfoRequest.Accept = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; | ||
|
||
// gets the response | ||
WebResponse userinfoResponse = await userinfoRequest.GetResponseAsync(); | ||
using (StreamReader userinfoResponseReader = new StreamReader(userinfoResponse.GetResponseStream())) | ||
{ | ||
// reads response body | ||
string userinfoResponseText = await userinfoResponseReader.ReadToEndAsync(); | ||
output(userinfoResponseText); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Appends the given string to the on-screen log, and the debug console. | ||
/// </summary> | ||
/// <param name="output">string to be appended</param> | ||
public void output(string output) | ||
{ | ||
Console.WriteLine(output); | ||
} | ||
|
||
/// <summary> | ||
/// Returns URI-safe data with a given input length. | ||
/// </summary> | ||
/// <param name="length">Input length (nb. output will be longer)</param> | ||
/// <returns></returns> | ||
public static string randomDataBase64url(uint length) | ||
{ | ||
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); | ||
byte[] bytes = new byte[length]; | ||
rng.GetBytes(bytes); | ||
return base64urlencodeNoPadding(bytes); | ||
} | ||
|
||
/// <summary> | ||
/// Returns the SHA256 hash of the input string. | ||
/// </summary> | ||
/// <param name="inputStirng"></param> | ||
/// <returns></returns> | ||
public static byte[] sha256(string inputStirng) | ||
{ | ||
byte[] bytes = Encoding.ASCII.GetBytes(inputStirng); | ||
SHA256Managed sha256 = new SHA256Managed(); | ||
return sha256.ComputeHash(bytes); | ||
} | ||
|
||
/// <summary> | ||
/// Base64url no-padding encodes the given input buffer. | ||
/// </summary> | ||
/// <param name="buffer"></param> | ||
/// <returns></returns> | ||
public static string base64urlencodeNoPadding(byte[] buffer) | ||
{ | ||
string base64 = Convert.ToBase64String(buffer); | ||
|
||
// Converts base64 to base64url. | ||
base64 = base64.Replace("+", "-"); | ||
base64 = base64.Replace("/", "_"); | ||
// Strips padding. | ||
base64 = base64.Replace("=", ""); | ||
|
||
return base64; | ||
} | ||
|
||
// Hack to bring the Console window to front. | ||
// ref: http://stackoverflow.com/a/12066376 | ||
|
||
[DllImport("kernel32.dll", ExactSpelling = true)] | ||
public static extern IntPtr GetConsoleWindow(); | ||
|
||
[DllImport("user32.dll")] | ||
[return: MarshalAs(UnmanagedType.Bool)] | ||
public static extern bool SetForegroundWindow(IntPtr hWnd); | ||
|
||
public void BringConsoleToFront() | ||
{ | ||
SetForegroundWindow(GetConsoleWindow()); | ||
} | ||
|
||
} | ||
} |
Oops, something went wrong.