Skip to content

Instantly share code, notes, and snippets.

@msfayed
Created August 27, 2023 18:20
Show Gist options
  • Save msfayed/10a7ad2f16a76d29eb09ccd48aa7ea1d to your computer and use it in GitHub Desktop.
Save msfayed/10a7ad2f16a76d29eb09ccd48aa7ea1d to your computer and use it in GitHub Desktop.

Revisions

  1. msfayed created this gist Aug 27, 2023.
    131 changes: 131 additions & 0 deletions oauth2-oidc-client.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,131 @@
    class Program
    {
    static async Task Main(string[] args)
    {
    var _usePKCE = false;

    // ======================================
    var clientId = "clientId";
    var clientSecret = "clientSecret";
    //_usePKCE = true; // for SPA/Mobile (without backend)
    // ======================================

    var authorizationEndpoint = "http://localhost:8090/authorize";
    var tokenEndpoint = "http://localhost:8090/token";

    var redirectUri = "http://localhost:8080/";
    var scope = "openid profile email"; // Adjust the scope based on your needs

    var state = Guid.NewGuid().ToString();

    var authorizeUrl = $"{authorizationEndpoint}?client_id={clientId}&redirect_uri={HttpUtility.UrlEncode(redirectUri)}&response_type=code&scope={System.Web.HttpUtility.UrlEncode(scope)}&state={state}";

    var code_verifier = "";
    var code_challenge = "";

    if (_usePKCE)
    {
    code_verifier = getCodeVerifier();
    code_challenge = getCodeChallenge(code_verifier);

    authorizeUrl += $"&code_challenge={code_challenge}&code_challenge_method=S256 ";
    }

    Clipboard.SetText(authorizeUrl);
    Console.WriteLine("Please open the following URL in your browser to authenticate: [ already in your clipboard ;) ] \n\n");
    Console.WriteLine(authorizeUrl + "\n\n");

    using (var listener = new HttpListener())
    {
    listener.Prefixes.Add(redirectUri);
    listener.Start();

    Console.WriteLine("Waiting for callback...");

    while (true)
    {
    var context = await listener.GetContextAsync();
    var request = context.Request;

    if (request.HttpMethod == "GET")
    {
    var queryParameters = HttpUtility.ParseQueryString(request.Url.Query);
    var authorizationCode = queryParameters["code"];
    var receivedState = queryParameters["state"];

    var tokenContent = "";
    if (receivedState == state)
    {
    // Exchange authorization code for access token
    Console.WriteLine($"Received authorization code: {authorizationCode}\n\n");

    var tokenClient = new System.Net.Http.HttpClient();

    var tokenRequest = new Dictionary<string, string>
    {
    { "grant_type", "authorization_code" },
    { "code", authorizationCode },
    { "redirect_uri", redirectUri },
    { "client_id", clientId }
    };

    if (_usePKCE)
    {
    tokenRequest.Add("code_verifier", code_verifier);
    }
    else
    {
    tokenRequest.Add("client_secret", clientSecret);
    }

    var tokenResponse = await tokenClient.PostAsync(tokenEndpoint, new FormUrlEncodedContent(tokenRequest));
    tokenContent = await tokenResponse.Content.ReadAsStringAsync();

    Console.WriteLine($"Token Response: {tokenContent}");
    }
    else
    {
    Console.WriteLine("Received state does not match.");
    }

    string responseString = "Authorization successful.\nJWT can be decoded at: https://jwt.io/ \nResponse:\n\n" + tokenContent;
    byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
    context.Response.AddHeader("Content-Type", "application/json; charset=utf-8");
    context.Response.ContentLength64 = buffer.Length;
    context.Response.OutputStream.Write(buffer, 0, buffer.Length);
    context.Response.Close();
    break;
    }
    }

    listener.Stop();
    }
    }

    static string getCodeVerifier()
    {
    var rng = RandomNumberGenerator.Create();

    var bytes = new byte[32];
    rng.GetBytes(bytes);

    // It is recommended to use a URL-safe string as code_verifier.
    // See section 4 of RFC 7636 for more details.
    return Convert.ToBase64String(bytes)
    .TrimEnd('=')
    .Replace('+', '-')
    .Replace('/', '_');
    }

    static string getCodeChallenge(string val)
    {
    using (var sha256 = SHA256.Create())
    {
    var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(val));
    return Convert.ToBase64String(challengeBytes)
    .TrimEnd('=')
    .Replace('+', '-')
    .Replace('/', '_');
    }
    }
    }