1
- // Copyright 2016 Google Inc.
1
+ // Copyright 2016 Google Inc.
2
2
//
3
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
4
// you may not use this file except in compliance with the License.
12
12
// See the License for the specific language governing permissions and
13
13
// limitations under the License.
14
14
15
+ using Newtonsoft . Json ;
15
16
using System ;
16
17
using System . Collections . Generic ;
17
- using System . Text ;
18
- using System . Threading . Tasks ;
19
- using Newtonsoft . Json ;
18
+ using System . Diagnostics ;
20
19
using System . IO ;
21
20
using System . Net ;
22
21
using System . Net . Sockets ;
23
- using System . Security . Cryptography ;
24
22
using System . Runtime . InteropServices ;
23
+ using System . Security . Cryptography ;
24
+ using System . Text ;
25
+ using System . Threading . Tasks ;
25
26
26
27
namespace OAuthConsoleApp
27
28
{
28
29
class Program
29
30
{
30
- static void Main ( string [ ] args )
31
+ const string AuthorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth" ;
32
+
33
+ static async Task < int > Main ( string [ ] args )
31
34
{
35
+ if ( args . Length != 2 )
36
+ {
37
+ Console . WriteLine ( "Required command line arguments: client-id client-secret" ) ;
38
+ return 1 ;
39
+ }
40
+ string clientId = args [ 0 ] ;
41
+ string clientSecret = args [ 1 ] ;
42
+
32
43
Console . WriteLine ( "+-----------------------+" ) ;
33
44
Console . WriteLine ( "| Sign in with Google |" ) ;
34
45
Console . WriteLine ( "+-----------------------+" ) ;
@@ -37,18 +48,13 @@ static void Main(string[] args)
37
48
Console . ReadKey ( ) ;
38
49
39
50
Program p = new Program ( ) ;
40
- p . doOAuth ( ) ;
51
+ await p . DoOAuthAsync ( clientId , clientSecret ) ;
41
52
53
+ Console . WriteLine ( "Press any key to exit..." ) ;
42
54
Console . ReadKey ( ) ;
55
+ return 0 ;
43
56
}
44
57
45
- // client configuration
46
- const string clientID = "581786658708-elflankerquo1a6vsckabbhn25hclla0.apps.googleusercontent.com" ;
47
- const string clientSecret = "3f6NggMbPtrmIBpgx-MK2xXK" ;
48
- const string authorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth" ;
49
- const string tokenEndpoint = "https://www.googleapis.com/oauth2/v4/token" ;
50
- const string userInfoEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo" ;
51
-
52
58
// ref http://stackoverflow.com/a/3978040
53
59
public static int GetRandomUnusedPort ( )
54
60
{
@@ -59,35 +65,35 @@ public static int GetRandomUnusedPort()
59
65
return port ;
60
66
}
61
67
62
- private async void doOAuth ( )
68
+ private async Task DoOAuthAsync ( string clientId , string clientSecret )
63
69
{
64
70
// Generates state and PKCE values.
65
- string state = randomDataBase64url ( 32 ) ;
66
- string code_verifier = randomDataBase64url ( 32 ) ;
67
- string code_challenge = base64urlencodeNoPadding ( sha256 ( code_verifier ) ) ;
68
- const string code_challenge_method = "S256" ;
71
+ string state = GenerateRandomDataBase64url ( 32 ) ;
72
+ string codeVerifier = GenerateRandomDataBase64url ( 32 ) ;
73
+ string codeChallenge = Base64UrlEncodeNoPadding ( Sha256Ascii ( codeVerifier ) ) ;
74
+ const string codeChallengeMethod = "S256" ;
69
75
70
76
// Creates a redirect URI using an available port on the loopback address.
71
- string redirectURI = string . Format ( "http://{0}:{1}/" , IPAddress . Loopback , GetRandomUnusedPort ( ) ) ;
72
- output ( "redirect URI: " + redirectURI ) ;
77
+ string redirectUri = $ "http://{ IPAddress . Loopback } : { GetRandomUnusedPort ( ) } /" ;
78
+ Log ( "redirect URI: " + redirectUri ) ;
73
79
74
80
// Creates an HttpListener to listen for requests on that redirect URI.
75
81
var http = new HttpListener ( ) ;
76
- http . Prefixes . Add ( redirectURI ) ;
77
- output ( "Listening.." ) ;
82
+ http . Prefixes . Add ( redirectUri ) ;
83
+ Log ( "Listening.." ) ;
78
84
http . Start ( ) ;
79
85
80
86
// Creates the OAuth 2.0 authorization request.
81
87
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}" ,
82
- authorizationEndpoint ,
83
- System . Uri . EscapeDataString ( redirectURI ) ,
84
- clientID ,
88
+ AuthorizationEndpoint ,
89
+ Uri . EscapeDataString ( redirectUri ) ,
90
+ clientId ,
85
91
state ,
86
- code_challenge ,
87
- code_challenge_method ) ;
92
+ codeChallenge ,
93
+ codeChallengeMethod ) ;
88
94
89
95
// Opens request in the browser.
90
- System . Diagnostics . Process . Start ( authorizationRequest ) ;
96
+ Process . Start ( authorizationRequest ) ;
91
97
92
98
// Waits for the OAuth authorization response.
93
99
var context = await http . GetContextAsync ( ) ;
@@ -97,71 +103,71 @@ private async void doOAuth()
97
103
98
104
// Sends an HTTP response to the browser.
99
105
var response = context . Response ;
100
- string responseString = string . Format ( " Please return to the app." ) ;
101
- var buffer = System . Text . Encoding . UTF8 . GetBytes ( responseString ) ;
106
+ string responseString = " Please return to the app." ;
107
+ byte [ ] buffer = Encoding . UTF8 . GetBytes ( responseString ) ;
102
108
response . ContentLength64 = buffer . Length ;
103
109
var responseOutput = response . OutputStream ;
104
- Task responseTask = responseOutput . WriteAsync ( buffer , 0 , buffer . Length ) . ContinueWith ( ( task ) =>
105
- {
106
- responseOutput . Close ( ) ;
107
- http . Stop ( ) ;
108
- Console . WriteLine ( "HTTP server stopped." ) ;
109
- } ) ;
110
+ await responseOutput . WriteAsync ( buffer , 0 , buffer . Length ) ;
111
+ responseOutput . Close ( ) ;
112
+ http . Stop ( ) ;
113
+ Log ( "HTTP server stopped." ) ;
110
114
111
115
// Checks for errors.
112
- if ( context . Request . QueryString . Get ( "error" ) != null )
116
+ string error = context . Request . QueryString . Get ( "error" ) ;
117
+ if ( error is object )
113
118
{
114
- output ( String . Format ( "OAuth authorization error: {0 }." , context . Request . QueryString . Get ( "error" ) ) ) ;
119
+ Log ( $ "OAuth authorization error: { error } .") ;
115
120
return ;
116
121
}
117
- if ( context . Request . QueryString . Get ( "code" ) == null
118
- || context . Request . QueryString . Get ( "state" ) == null )
122
+ if ( context . Request . QueryString . Get ( "code" ) is null
123
+ || context . Request . QueryString . Get ( "state" ) is null )
119
124
{
120
- output ( "Malformed authorization response. " + context . Request . QueryString ) ;
125
+ Log ( $ "Malformed authorization response. { context . Request . QueryString } " ) ;
121
126
return ;
122
127
}
123
128
124
129
// extracts the code
125
130
var code = context . Request . QueryString . Get ( "code" ) ;
126
- var incoming_state = context . Request . QueryString . Get ( "state" ) ;
131
+ var incomingState = context . Request . QueryString . Get ( "state" ) ;
127
132
128
133
// Compares the receieved state to the expected value, to ensure that
129
134
// this app made the request which resulted in authorization.
130
- if ( incoming_state != state )
135
+ if ( incomingState != state )
131
136
{
132
- output ( String . Format ( "Received request with invalid state ({0 })" , incoming_state ) ) ;
137
+ Log ( $ "Received request with invalid state ({ incomingState } )") ;
133
138
return ;
134
139
}
135
- output ( "Authorization code: " + code ) ;
140
+ Log ( "Authorization code: " + code ) ;
136
141
137
142
// Starts the code exchange at the Token Endpoint.
138
- performCodeExchange ( code , code_verifier , redirectURI ) ;
143
+ await ExchangeCodeForTokensAsync ( code , codeVerifier , redirectUri , clientId , clientSecret ) ;
139
144
}
140
145
141
- async void performCodeExchange ( string code , string code_verifier , string redirectURI )
146
+ async Task ExchangeCodeForTokensAsync ( string code , string codeVerifier , string redirectUri , string clientId , string clientSecret )
142
147
{
143
- output ( "Exchanging code for tokens..." ) ;
148
+ Log ( "Exchanging code for tokens..." ) ;
144
149
145
150
// builds the request
146
- string tokenRequestURI = "https://www.googleapis.com/oauth2/v4/token" ;
151
+ string tokenRequestUri = "https://www.googleapis.com/oauth2/v4/token" ;
147
152
string tokenRequestBody = string . Format ( "code={0}&redirect_uri={1}&client_id={2}&code_verifier={3}&client_secret={4}&scope=&grant_type=authorization_code" ,
148
153
code ,
149
- System . Uri . EscapeDataString ( redirectURI ) ,
150
- clientID ,
151
- code_verifier ,
154
+ Uri . EscapeDataString ( redirectUri ) ,
155
+ clientId ,
156
+ codeVerifier ,
152
157
clientSecret
153
158
) ;
154
159
155
160
// sends the request
156
- HttpWebRequest tokenRequest = ( HttpWebRequest ) WebRequest . Create ( tokenRequestURI ) ;
161
+ HttpWebRequest tokenRequest = ( HttpWebRequest ) WebRequest . Create ( tokenRequestUri ) ;
157
162
tokenRequest . Method = "POST" ;
158
163
tokenRequest . ContentType = "application/x-www-form-urlencoded" ;
159
164
tokenRequest . Accept = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" ;
160
- byte [ ] _byteVersion = Encoding . ASCII . GetBytes ( tokenRequestBody ) ;
161
- tokenRequest . ContentLength = _byteVersion . Length ;
162
- Stream stream = tokenRequest . GetRequestStream ( ) ;
163
- await stream . WriteAsync ( _byteVersion , 0 , _byteVersion . Length ) ;
164
- stream . Close ( ) ;
165
+ byte [ ] tokenRequestBodyBytes = Encoding . ASCII . GetBytes ( tokenRequestBody ) ;
166
+ tokenRequest . ContentLength = tokenRequestBodyBytes . Length ;
167
+ using ( Stream requestStream = tokenRequest . GetRequestStream ( ) )
168
+ {
169
+ await requestStream . WriteAsync ( tokenRequestBodyBytes , 0 , tokenRequestBodyBytes . Length ) ;
170
+ }
165
171
166
172
try
167
173
{
@@ -176,8 +182,8 @@ async void performCodeExchange(string code, string code_verifier, string redirec
176
182
// converts to dictionary
177
183
Dictionary < string , string > tokenEndpointDecoded = JsonConvert . DeserializeObject < Dictionary < string , string > > ( responseText ) ;
178
184
179
- string access_token = tokenEndpointDecoded [ "access_token" ] ;
180
- userinfoCall ( access_token ) ;
185
+ string accessToken = tokenEndpointDecoded [ "access_token" ] ;
186
+ await RequestUserInfoAsync ( accessToken ) ;
181
187
}
182
188
}
183
189
catch ( WebException ex )
@@ -187,31 +193,30 @@ async void performCodeExchange(string code, string code_verifier, string redirec
187
193
var response = ex . Response as HttpWebResponse ;
188
194
if ( response != null )
189
195
{
190
- output ( "HTTP: " + response . StatusCode ) ;
196
+ Log ( "HTTP: " + response . StatusCode ) ;
191
197
using ( StreamReader reader = new StreamReader ( response . GetResponseStream ( ) ) )
192
198
{
193
199
// reads response body
194
200
string responseText = await reader . ReadToEndAsync ( ) ;
195
- output ( responseText ) ;
201
+ Log ( responseText ) ;
196
202
}
197
203
}
198
204
199
205
}
200
206
}
201
207
}
202
208
203
-
204
- async void userinfoCall ( string access_token )
209
+ private async Task RequestUserInfoAsync ( string accessToken )
205
210
{
206
- output ( "Making API Call to Userinfo..." ) ;
211
+ Log ( "Making API Call to Userinfo..." ) ;
207
212
208
213
// builds the request
209
- string userinfoRequestURI = "https://www.googleapis.com/oauth2/v3/userinfo" ;
214
+ string userinfoRequestUri = "https://www.googleapis.com/oauth2/v3/userinfo" ;
210
215
211
216
// sends the request
212
- HttpWebRequest userinfoRequest = ( HttpWebRequest ) WebRequest . Create ( userinfoRequestURI ) ;
217
+ HttpWebRequest userinfoRequest = ( HttpWebRequest ) WebRequest . Create ( userinfoRequestUri ) ;
213
218
userinfoRequest . Method = "GET" ;
214
- userinfoRequest . Headers . Add ( string . Format ( "Authorization: Bearer {0}" , access_token ) ) ;
219
+ userinfoRequest . Headers . Add ( string . Format ( "Authorization: Bearer {0}" , accessToken ) ) ;
215
220
userinfoRequest . ContentType = "application/x-www-form-urlencoded" ;
216
221
userinfoRequest . Accept = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" ;
217
222
@@ -221,15 +226,15 @@ async void userinfoCall(string access_token)
221
226
{
222
227
// reads response body
223
228
string userinfoResponseText = await userinfoResponseReader . ReadToEndAsync ( ) ;
224
- output ( userinfoResponseText ) ;
229
+ Log ( userinfoResponseText ) ;
225
230
}
226
231
}
227
232
228
233
///
229
234
/// Appends the given string to the on-screen log, and the debug console.
230
235
///
231
- /// string to be appended
232
- public void output ( string output )
236
+
237
+ private void Log ( string output )
233
238
{
234
239
Console . WriteLine ( output ) ;
235
240
}
@@ -239,32 +244,32 @@ public void output(string output)
239
244
///
240
245
/// Input length (nb. output will be longer)
241
246
///
242
- public static string randomDataBase64url ( uint length )
247
+ private static string GenerateRandomDataBase64url ( uint length )
243
248
{
244
249
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider ( ) ;
245
250
byte [ ] bytes = new byte [ length ] ;
246
251
rng . GetBytes ( bytes ) ;
247
- return base64urlencodeNoPadding ( bytes ) ;
252
+ return Base64UrlEncodeNoPadding ( bytes ) ;
248
253
}
249
254
250
255
///
251
- /// Returns the SHA256 hash of the input string.
256
+ /// Returns the SHA256 hash of the input string, which is assumed to be ASCII .
252
257
///
253
-
254
- ///
255
- public static byte [ ] sha256 ( string inputStirng )
258
+ private static byte [ ] Sha256Ascii ( string text )
256
259
{
257
- byte [ ] bytes = Encoding . ASCII . GetBytes ( inputStirng ) ;
258
- SHA256Managed sha256 = new SHA256Managed ( ) ;
259
- return sha256 . ComputeHash ( bytes ) ;
260
+ byte [ ] bytes = Encoding . ASCII . GetBytes ( text ) ;
261
+ using ( SHA256Managed sha256 = new SHA256Managed ( ) )
262
+ {
263
+ return sha256 . ComputeHash ( bytes ) ;
264
+ }
260
265
}
261
266
262
267
///
263
268
/// Base64url no-padding encodes the given input buffer.
264
269
///
265
270
266
271
///
267
- public static string base64urlencodeNoPadding ( byte [ ] buffer )
272
+ private static string Base64UrlEncodeNoPadding ( byte [ ] buffer )
268
273
{
269
274
string base64 = Convert . ToBase64String ( buffer ) ;
270
275
0 commit comments