private void ExecuteFullAuth() { // Generate state and PKCE values. expectedState = CryptoUtils.RandomDataBase64Uri(32); codeVerifier = CryptoUtils.RandomDataBase64Uri(32); var codeVerifierHash = CryptoUtils.Sha256(codeVerifier); var codeChallenge = CryptoUtils.Base64UriEncodeNoPadding(codeVerifierHash); // Creates a redirect URI using an available port on the loopback address. redirectUri = string.Format("{0}:{1}", LOOPBACK_URI, GetRandomUnusedPort()); // Listen for requests on the redirect URI. var httpListener = new HttpListener(); httpListener.Prefixes.Add(redirectUri + '/'); httpListener.Start(); // Create the OAuth 2.0 authorization request. // https://developers.google.com/identity/protocols/OAuth2WebServer#creatingclient var authRequest = string.Format("{0}?response_type=code&scope={1}&redirect_uri={2}&client_id={3}&state={4}&code_challenge={5}&code_challenge_method={6}" + "&access_type=offline" + // Forces to return a refresh token at the auth code exchange phase. "&approval_prompt=force", // Forces to show consent screen for each auth request. Needed to return refresh tokens on consequent auth runs. settings.AuthCredentials.AuthUri, settings.AccessScope, Uri.EscapeDataString(redirectUri), settings.AuthCredentials.ClientId, expectedState, codeChallenge, GoogleDriveSettings.CODE_CHALLENGE_METHOD); // Open request in the browser. Application.OpenURL(authRequest); // Wait for the authorization response. var context = httpListener.GetContext(); // Send an HTTP response to the browser to notify the user to close the browser. var response = context.Response; var responseString = settings.LoopbackResponseHtml; var buffer = System.Text.Encoding.UTF8.GetBytes(responseString); response.ContentLength64 = buffer.Length; var responseOutput = response.OutputStream; responseOutput.Write(buffer, 0, buffer.Length); responseOutput.Close(); httpListener.Stop(); // Check for errors. if (context.Request.QueryString.Get("error") != null) { Debug.LogError(string.Format("UnityGoogleDrive: OAuth authorization error: {0}.", context.Request.QueryString.Get("error"))); HandleProvideAccessTokenComplete(true); return; } if (context.Request.QueryString.Get("code") == null || context.Request.QueryString.Get("state") == null) { Debug.LogError("UnityGoogleDrive: Malformed authorization response. " + context.Request.QueryString); HandleProvideAccessTokenComplete(true); return; } // Extract the authorization code. authorizationCode = context.Request.QueryString.Get("code"); var incomingState = context.Request.QueryString.Get("state"); // Compare the receieved state to the expected value, to ensure that // this app made the request which resulted in authorization. if (incomingState != expectedState) { Debug.LogError(string.Format("UnityGoogleDrive: Received request with invalid state ({0}).", incomingState)); HandleProvideAccessTokenComplete(true); return; } // Exchange the authorization code for tokens. authCodeExchanger.ExchangeAuthCode(authorizationCode, codeVerifier, redirectUri); }