@@ -40,64 +40,87 @@ public enum OAuthResponseType
4040 /// </summary>
4141 /// <example>
4242 /// <para>
43- /// This shows an example of how to use the token, or implicit grant, flow.
44- /// This code is part of a XAML window that contains a WebBrowser object as <c>this.Browser</c>
43+ /// This shows an example of how to use the token flow. This is part of a Windows Console or WPF app.
4544 /// </para>
4645 /// <para>
47- /// The <c>Start </c> method calls <see cref="DropboxOAuth2Helper.GetAuthorizeUri" /> to create the URI that the browser component
48- /// navigate to; the response type is set to <see cref="OAuthResponseType.Token"/> to create a URI for the token flow.
46+ /// The <c>GetAccessToken() </c> method calls <see cref="DropboxOAuth2Helper.GetAuthorizeUri" /> to create the URI with response type
47+ /// set to <see cref="OAuthResponseType.Token"/> for token flow.
4948 /// </para>
5049 /// <para>
51- /// The exact value of the redirect URI is not important with the code flow, only that it is registered in the
52- /// <a href="https://www.dropbox.com/developers/apps">App Console</a>; it is common to use a <c>localhost</c>
53- /// URI for use within a client token flow like this.
50+ /// <see cref="Guid.NewGuid"/> is called to generate a random string to use as the state argument, this value can also be used to
51+ /// store application context and prevent cross-site request forgery.
5452 /// </para>
5553 /// <para>
56- /// The <c>BrowserNavigating</c> method has been attached to the <c>Navigating</c> event on the <c>WebBrowser</c> object.
57- /// It first checks if the URI to which the browser is navigating starts with the redirect uri provided in the call to
58- /// <see cref="DropboxOAuth2Helper.GetAuthorizeUri" /> — it is important not to prevent other navigation which may happen within the
59- /// authorization flow — if the URI matches, then the code uses <see cref="DropboxOAuth2Helper.ParseTokenFragment"/> to parse the
60- /// <see cref="OAuth2Response"/> from the fragment component of the redirected URI. The <see cref="OAuth2Response.AccessToken" />
61- /// will then be used to construct an instance of <see cref="DropboxClient"/>.
54+ /// A <see cref="HttpListener"/> is created to listen to the <c>RedirectUri</c> which will later receive redirect callback from
55+ /// the server. <see cref="System.Diagnostics.Process.Start"/> is called to launch a native browser and navigate user to the authorize
56+ /// URI. The <c>RedirectUri</c> needs to be registered at <a href="https://www.dropbox.com/developers/apps">App Console</a>. It's
57+ /// common to use value like <c>http://127.0.0.1:{some_avaialble_port}</c>.
58+ /// </para>
59+ /// <para>
60+ /// After user successfully authorizes the request, <c>HandleOAuth2Redirect</c> receives the redirect callback which contains state
61+ /// and access token as URL fragment. Since the server cannot receive URL fragment directly, it calls <c>RespondPageWithJSRedirect</c>
62+ /// to respond with a HTML page which runs JS code and sends URL fragment as query string parameter to a separate <c>JSRedirect</c> endpoint.
63+ /// </para>
64+ /// <para>
65+ /// <c>HandleJSRedirect</c> is called to handle redirect from JS code and processes OAuth response from query string.
66+ /// This returns an <see cref="OAuth2Response"/> containing the access token that will be passed to the <see cref="DropboxClient"/> constructor.
6267 /// </para>
6368 /// <code>
64- /// private void Start(string appKey )
69+ /// private async Task HandleOAuth2Redirect(HttpListener http )
6570 /// {
66- /// this.oauth2State = Guid.NewGuid().ToString("N");
67- /// Uri authorizeUri = DropboxOAuth2Helper.GetAuthorizeUri(OauthResponseType.Token, appKey, RedirectUri, state: oauth2State);
68- /// this.Browser.Navigate(authorizeUri);
69- /// }
71+ /// var context = await http.GetContextAsync();
7072 ///
71- /// private void BrowserNavigating(object sender, NavigatingCancelEventArgs e)
72- /// {
73- /// if (!e.Uri.ToString().StartsWith(RedirectUri, StringComparison.OrdinalIgnoreCase))
73+ /// // We only care about request to RedirectUri endpoint.
74+ /// while (context.Request.Url.AbsolutePath != RedirectUri.AbsolutePath)
7475 /// {
75- /// // we need to ignore all navigation that isn't to the redirect uri.
76- /// return;
76+ /// context = await http.GetContextAsync();
7777 /// }
7878 ///
79- /// try
80- /// {
81- /// OAuth2Response result = DropboxOAuth2Helper.ParseTokenFragment(e.Uri);
82- /// if (result.State != this.oauth2State)
83- /// {
84- /// // The state in the response doesn't match the state in the request.
85- /// return;
86- /// }
79+ /// // Respond with a HTML page which runs JS to send URl fragment.
80+ /// RespondPageWithJSRedirect();
81+ /// }
8782 ///
88- /// this.AccessToken = result.AccessToken;
89- /// this.Uid = result.Uid;
90- /// this.Result = true;
91- /// }
92- /// catch (ArgumentException)
83+ ///
84+ /// private async Task<OAuth2Response> HandleJSRedirect(HttpListener http)
85+ /// {
86+ /// var context = await http.GetContextAsync();
87+ ///
88+ /// // We only care about request to TokenRedirectUri endpoint.
89+ /// while (context.Request.Url.AbsolutePath != JSRedirectUri.AbsolutePath)
9390 /// {
94- /// // There was an error in the URI passed to ParseTokenFragment
91+ /// context = await http.GetContextAsync();
9592 /// }
96- /// finally
93+ ///
94+ /// var redirectUri = new Uri(context.Request.QueryString["url_with_fragment"]);
95+ ///
96+ /// var result = DropboxOAuth2Helper.ParseTokenFragment(redirectUri);
97+ ///
98+ /// return result;
99+ /// }
100+ ///
101+ /// private async Task GetAccessToken() {
102+ /// var state = Guid.NewGuid().ToString("N");
103+ /// var authorizeUri = DropboxOAuth2Helper.GetAuthorizeUri(OAuthResponseType.Code, ApiKey, new Uri(RedirectUri), state: state);
104+ ///
105+ /// var http = new HttpListener();
106+ /// http.Prefixes.Add(RedirectUri);
107+ /// http.Start();
108+ ///
109+ /// System.Diagnostics.Process.Start(authorizeUri.ToString());
110+ ///
111+ /// // Handle OAuth redirect and send URL fragment to local server using JS.
112+ /// await HandleOAuth2Redirect(http);
113+ ///
114+ /// // Handle redirect from JS and process OAuth response.
115+ /// var result = await HandleJSRedirect(http);
116+ ///
117+ /// if (result.State != state)
97118 /// {
98- /// e.Cancel = true;
99- /// this.Close() ;
119+ /// // The state in the response doesn't match the state in the request.
120+ /// return null ;
100121 /// }
122+ ///
123+ /// Settings.Default.AccessToken = result.AccessToken;
101124 /// }
102125 /// </code>
103126 /// <para>
0 commit comments