Skip to content

Instantly share code, notes, and snippets.

@LasseSkogland
Last active February 27, 2021 22:26
Show Gist options
  • Select an option

  • Save LasseSkogland/b65ee0df912fef2397e920cd507771a8 to your computer and use it in GitHub Desktop.

Select an option

Save LasseSkogland/b65ee0df912fef2397e920cd507771a8 to your computer and use it in GitHub Desktop.
Implementation of the new DuoWeb SDK v4 (frameless) in C# (Should support Universal Prompt)
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Web;
namespace Duo {
public class Web {
private readonly HttpClient Client;
private readonly string ClientId;
private readonly string SecretKey;
private readonly string ApiHostame;
public Web(string clientId, string secretKey, string apiHostname) {
Client = new HttpClient();
ClientId = clientId;
SecretKey = secretKey;
ApiHostame = apiHostname;
}
public async Task<ExpandoObject> HealthCheck() {
var url = $"https://{ApiHostame}/oauth/v1/health_check";
var jwt = EncodeJwt(CreateAssertion(url), "HS512", SecretKey);
var formContent = new FormUrlEncodedContent(new Dictionary<string, string>() {
{ "client_id", ClientId },
{ "client_assertion", jwt}
});
var res = await Client.PostAsync(url, formContent);
var jsonResponse = await res.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<ExpandoObject>(jsonResponse);
}
public string GetAuthorizeUrl(string username, string redirectUri) {
var url = $"https://{ApiHostame}/oauth/v1/authorize";
var jwt = EncodeJwt(new {
response_type = "code",
scope = "openid",
exp = ToUnixTimestamp(DateTime.UtcNow.AddMinutes(5)),
client_id = ClientId,
redirect_uri = redirectUri,
state = GetRandomAlphanumericString(40),
duo_uname = username
}, "HS512", SecretKey);
return $"{url}?response_type=code&client_id={ClientId}&request={HttpUtility.UrlEncode(jwt)}&redirect_uri={HttpUtility.UrlEncode(redirectUri)}&scope=openid";
}
public async Task<ExpandoObject> Token(string code, string redirectUri) {
var url = $"https://{ApiHostame}/oauth/v1/token";
var jwt = EncodeJwt(CreateAssertion(url), "HS512", SecretKey);
var formContent = new FormUrlEncodedContent(new Dictionary<string, string>() {
{ "grant_type", "authorization_code" },
{ "code", code },
{ "redirect_uri", redirectUri },
{ "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" },
{ "client_assertion", jwt }
});
var res = await Client.PostAsync(url, formContent);
var jsonResponse = await res.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<ExpandoObject>(jsonResponse);
}
#region Utility functions
private static readonly Random _random = new Random();
private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
private static string ToBase64String(string input) => Convert.ToBase64String(Encoding.UTF8.GetBytes(input));
private static int ToUnixTimestamp(DateTime dateTime) => (int)Math.Floor((dateTime.ToUniversalTime() - epoch).TotalSeconds);
private static string GetRandomAlphanumericString(int length) {
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[_random.Next(s.Length)]).ToArray());
}
private static dynamic CreateAssertion(string url) {
var utcNow = ToUnixTimestamp(DateTime.UtcNow);
return new {
iss = ClientId,
sub = ClientId,
aud = url,
exp = utcNow + 300,
jti = GetRandomAlphanumericString(40),
iat = utcNow
};
}
private static string Hmac512Sign(string skey, string data, string alg) {
byte[] key_bytes = Encoding.UTF8.GetBytes(skey);
HMAC hmac = alg switch {
"HS512" => new HMACSHA512(key_bytes),
"HS384" => new HMACSHA384(key_bytes),
"HS256" => new HMACSHA256(key_bytes),
_ => new HMACSHA1(),
};
using (hmac) {
byte[] data_bytes = Encoding.UTF8.GetBytes(data);
hmac.ComputeHash(data_bytes);
return Convert.ToBase64String(hmac.Hash);
}
}
public static string EncodeJwt(dynamic payload, string alg, string secretKey) {
var jsonHeader = JsonSerializer.Serialize(new { alg, type = "JWT" });
string jsonPayload = JsonSerializer.Serialize(payload);
var body = ToBase64String(jsonHeader) + '.' + ToBase64String(jsonPayload);
var signature = Hmac512Sign(secretKey, body, alg);
return body + '.' + signature;
}
public static string[] DecodeJwt(string jwtToken) {
string[] parts = jwtToken.Split('.');
for (int i = 0; i < parts.Length; i++) {
var part = parts[i];
part = part.Replace('_', '/').Replace('-', '+');
switch (part.Length % 4) {
case 2: part += "=="; break;
case 3: part += "="; break;
}
if (i == 2) {
parts[i] = part;
}
else {
parts[i] = Encoding.UTF8.GetString(Convert.FromBase64String(part));
}
}
return parts;
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment