diff --git a/APSToolkit/Auth.cs b/APSToolkit/Auth.cs
index 894fc12..24c1f67 100644
--- a/APSToolkit/Auth.cs
+++ b/APSToolkit/Auth.cs
@@ -2,9 +2,12 @@
using System.Diagnostics;
using System.Net;
+using System.Security.Cryptography;
using System.Text;
+using System.Text.RegularExpressions;
using Autodesk.Forge;
using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
namespace APSToolkit;
@@ -14,13 +17,23 @@ public class Auth
private string? ClientSecret { get; set; }
private Token Token { get; set; }
- public Auth(string? clientId, string? clientSecret)
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ ///
+ public Auth(string? clientId=null, string? clientSecret=null)
{
this.ClientId = clientId;
this.ClientSecret = clientSecret;
this.Token = new Token();
}
+ ///
+ /// Initializes a new instance of the class.
+ /// If the client ID and client secret are not provided, the constructor retrieves them from the environment variables.
+ ///
public Auth()
{
this.ClientId = Environment.GetEnvironmentVariable("APS_CLIENT_ID",EnvironmentVariableTarget.User);
@@ -53,7 +66,12 @@ public string GetClientId()
{
throw new Exception("Missing APS_CLIENT_ID environment variable.");
}
-
+ Scope[] scope = new Scope[]
+ {
+ Scope.DataRead, Scope.DataWrite, Scope.DataCreate, Scope.DataSearch, Scope.BucketCreate,
+ Scope.BucketRead, Scope.CodeAll,
+ Scope.BucketUpdate, Scope.BucketDelete
+ };
return clientId;
}
@@ -171,7 +189,6 @@ public async Task Get3LeggedToken(string? callbackUrl = null, string? sco
Environment.SetEnvironmentVariable("APS_REFRESH_TOKEN", Token.RefreshToken, EnvironmentVariableTarget.User);
return Token;
}
-
private async Task HandleCallback(string callbackUrl, string? code)
{
var tokenUrl = "https://developer.api.autodesk.com/authentication/v2/token";
@@ -204,6 +221,119 @@ private void OpenDefaultBrowser(string url)
Console.WriteLine($"Error opening default browser: {ex.Message}");
}
}
+ public async Task Get3LeggedTokenPkce(string? callbackUrl = null, string? scopes = null)
+ {
+ if (string.IsNullOrEmpty(scopes))
+ {
+ scopes = "data:read";
+ }
+
+ if (string.IsNullOrEmpty(callbackUrl))
+ {
+ callbackUrl = "http://localhost:8080/api/auth/callback";
+ }
+ string codeVerifier = RandomString(64);
+ string codeChallenge = GenerateCodeChallenge(codeVerifier);
+ string authUrl = $"https://developer.api.autodesk.com/authentication/v2/authorize?response_type=code&client_id={ClientId}&redirect_uri={callbackUrl}&scope={scopes}&prompt=login&code_challenge={codeChallenge}&code_challenge_method=S256";
+ OpenDefaultBrowser(authUrl);
+ // get prefix from callbackUrl just get http://localhost:8080/api/auth/ from http://localhost:8080/api/auth/callback
+ string prefix = callbackUrl.Substring(0, callbackUrl.LastIndexOf('/'))+"/";
+ var listenerTask = CallListener(prefix, codeVerifier, callbackUrl, scopes);
+ await listenerTask;
+ Environment.SetEnvironmentVariable("APS_REFRESH_TOKEN", Token.RefreshToken, EnvironmentVariableTarget.User);
+ return Token;
+ }
+ private async Task CallListener(string prefix,string codeVerifier,string callbackUrl,string scopes)
+ {
+ if (!HttpListener.IsSupported)
+ {
+ throw new NotSupportedException("HttpListener is not supported in this context!");
+ }
+ if (prefix == null || prefix.Length == 0)
+ throw new ArgumentException("prefixes");
+ HttpListener listener = new HttpListener();
+ listener.Prefixes.Add(prefix);
+ listener.Start();
+ HttpListenerContext context = await listener.GetContextAsync();
+ HttpListenerRequest request = context.Request;
+ // Obtain a response object.
+ HttpListenerResponse response = context.Response;
+
+ try
+ {
+ string? authCode = request.Url?.Query.ToString().Split('=')[1];
+ await GetPkceToken(authCode,codeVerifier,callbackUrl,scopes);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+
+ // Construct a response.
+ string responseString = " Authentication successful. You can close this window now.";
+ byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
+ // Get a response stream and write the response to it.
+ response.ContentLength64 = buffer.Length;
+ System.IO.Stream output = response.OutputStream;
+ output.Write(buffer, 0, buffer.Length);
+ // You must close the output stream.
+ output.Close();
+ listener.Stop();
+ }
+ private async Task GetPkceToken(string? authCode,string codeVerifier,string callbackUrl,string scopes)
+ {
+ try
+ {
+ var client = new HttpClient();
+ var request = new HttpRequestMessage
+ {
+ Method = HttpMethod.Post,
+ RequestUri = new Uri("https://developer.api.autodesk.com/authentication/v2/token"),
+ Content = new FormUrlEncodedContent(new Dictionary
+ {
+ { "client_id", ClientId },
+ { "code_verifier", codeVerifier },
+ { "code", authCode},
+ { "scope", scopes },
+ { "grant_type", "authorization_code" },
+ { "redirect_uri", callbackUrl }
+ }),
+ };
+
+ using (var response = await client.SendAsync(request))
+ {
+ response.EnsureSuccessStatusCode();
+ string bodystring = await response.Content.ReadAsStringAsync();
+ JObject bodyjson = JObject.Parse(bodystring);
+ this.Token.AccessToken = bodyjson["access_token"]!.Value();
+ this.Token.TokenType = bodyjson["token_type"]!.Value();
+ this.Token.ExpiresIn = bodyjson["expires_in"]!.Value();
+ this.Token.RefreshToken = bodyjson["refresh_token"]!.Value();
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ }
+ private string RandomString(int length)
+ {
+ Random random = new Random();
+ const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ return new string(Enumerable.Repeat(chars, length)
+ .Select(s => s[random.Next(s.Length)]).ToArray());
+ }
+
+ private string GenerateCodeChallenge(string codeVerifier)
+ {
+ var sha256 = SHA256.Create();
+ var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
+ var b64Hash = Convert.ToBase64String(hash);
+ var code = Regex.Replace(b64Hash, "\\+", "-");
+ code = Regex.Replace(code, "\\/", "_");
+ code = Regex.Replace(code, "=+$", "");
+ return code;
+ }
///
/// Refreshes a 3-legged access token from the Autodesk Forge API using the refresh token grant type.
diff --git a/APSToolkitUnit/AuthTest.cs b/APSToolkitUnit/AuthTest.cs
index 78af2a6..d1e5e90 100644
--- a/APSToolkitUnit/AuthTest.cs
+++ b/APSToolkitUnit/AuthTest.cs
@@ -26,9 +26,19 @@ public void TestAuthentication2Leg()
public void TestAuthentication3Leg()
{
Token = Auth.Get3LeggedToken().Result;
+ Assert.IsNotNull(Token.AccessToken);
Assert.IsNotEmpty(Token.AccessToken);
}
-
+ [Test]
+ public Task TestAuthentication3LegPkce()
+ {
+ string clientId = Environment.GetEnvironmentVariable("APS_CLIENT_ID_PKCE", EnvironmentVariableTarget.User);
+ Auth = new Auth(clientId);
+ Token = Auth.Get3LeggedTokenPkce().Result;
+ Assert.IsNotNull(Token.AccessToken);
+ Assert.IsNotEmpty(Token.RefreshToken);
+ return Task.CompletedTask;
+ }
[Test]
public Task TestRefresh3LegToken()
{