Skip to content

Instantly share code, notes, and snippets.

@YannickRe
Created August 27, 2020 22:45
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save YannickRe/46a4d7b7f8c4f2ce21de7c206d307ab5 to your computer and use it in GitHub Desktop.
Save YannickRe/46a4d7b7f8c4f2ce21de7c206d307ab5 to your computer and use it in GitHub Desktop.
using Dalion.HttpMessageSigning.Signing;
using Dalion.HttpMessageSigning;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Net;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
namespace Services
{
internal class MagdaAccessToken
{
internal MagdaAccessToken()
{
this.TokenDate = DateTime.Now;
}
[JsonProperty("access_token")]
public string AccessToken { get; set; }
public DateTime ExpireDate
{
get
{
return this.TokenDate.AddSeconds(this.ExpiresIn);
}
}
public DateTime TokenDate { get; private set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("scope")]
public string Scope { get; set; }
}
public class MagdaTokenBasedServicesExample
{
private readonly string _keyId = "rsa-key-1";
private readonly string _clientId = "1234"; //TODO: Provide your client id
private readonly string _authUrl = "https://beta.oauth.vlaanderen.be/authorization/ws/oauth/v2/token";
private readonly string _messagesEndpointUrl = "https://iv.api.tni-vlaanderen.be/api/v1/messages/messages";
private MagdaAccessToken _accessToken;
private X509Certificate2 _clientCertificate;
private IRequestSignerFactory _requestSignerFactory;
private HttpClient _httpClient;
private JwtSecurityTokenHandler _tokenHandler;
public MagdaTokenBasedServicesExample(IRequestSignerFactory requestSignerFactory)
{
this._requestSignerFactory = requestSignerFactory;
this._clientCertificate = null; // TODO: Load your VO-DCB client certificate (pfx)
var httpClientHandler = new HttpClientHandler();
httpClientHandler.ClientCertificates.Add(this._clientCertificate);
this._httpClient = new HttpClient(httpClientHandler);
this._tokenHandler = new JwtSecurityTokenHandler();
}
public async Task<HttpResponseMessage> SendMessage()
{
if (this._accessToken == null || (this._accessToken != null && this._accessToken.ExpireDate <= DateTime.Now.AddMinutes(1)))
this._accessToken = await this.GetAccessToken();
var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri(this._messagesEndpointUrl)
};
await SignHttpRequestMessage(request);
// Add additional payload properties here
// Like MessageToSend
return await this._httpClient.SendAsync(request);
}
private async Task SignHttpRequestMessage(HttpRequestMessage request)
{
request.Headers.Add("signature-public-key", WrapCertificateInJWK(this._clientCertificate, _keyId));
request.Headers.Date = DateTimeOffset.UtcNow;
SigningSettings settings = new SigningSettings
{
SignatureAlgorithm = SignatureAlgorithm.CreateForSigning(this._clientCertificate, HashAlgorithmName.SHA512),
DigestHashAlgorithm = HashAlgorithmName.SHA512,
EnableNonce = false,
Headers = new[]
{
new HeaderName("signature-public-key")
},
UseDeprecatedAlgorithmParameter = true
};
var requestSigner = this._requestSignerFactory.Create(_keyId, settings);
await requestSigner.Sign(request);
request.Headers.Add("Signature", request.Headers.Authorization.Parameter);
request.Headers.Add("x-correlation-id", Guid.NewGuid().ToString());
request.Headers.Add("Accept", "application/json");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", this._accessToken.AccessToken);
}
private string WrapCertificateInJWK(X509Certificate2 certificate, string keyName)
{
var x509SigningKey = new X509SigningCredentials(certificate);
var x509Key = new X509SecurityKey(certificate);
var cert64 = Convert.ToBase64String(x509Key.Certificate.RawData);
var pubKey = x509Key.PublicKey as RSA;
var parameters = pubKey.ExportParameters(false);
var exponent = Convert.ToBase64String(parameters.Exponent);
var modulus = Convert.ToBase64String(parameters.Modulus);
var webKey = new JsonWebKey
{
Kty = "RSA",
Use = "sig",
Kid = keyName,
X5t = x509Key.Certificate.Thumbprint,
E = exponent,
N = modulus,
Alg = x509SigningKey.Algorithm
};
webKey.X5c.Add(cert64);
var simplifiedWebKey = new { kty = webKey.Kty, kid = webKey.Kid, n = webKey.N, e = webKey.E, x5c = webKey.X5c };
return JsonConvert.SerializeObject(simplifiedWebKey);
}
private async Task<MagdaAccessToken> GetAccessToken()
{
var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri(this._authUrl)
};
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
List<string> scopes = new List<string>() {
"msg_mailbox_v1_P",
"msg_msg_v1_P",
"msg_statuses_v1_P"
};
Dictionary<string, string> postParams = new Dictionary<string, string>();
postParams.Add("grant_type", "client_credentials");
postParams.Add("scope", string.Join(" ", scopes));
postParams.Add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
postParams.Add("client_assertion", this.GetAuthJwtToken(string.Join(" ", scopes)));
request.Content = new FormUrlEncodedContent(postParams);
var response = await this._httpClient.SendAsync(request);
var responseContent = await response.Content.ReadAsStringAsync();
switch (response.StatusCode)
{
case HttpStatusCode.OK:
return JsonConvert.DeserializeObject<MagdaAccessToken>(responseContent);
default:
throw new Exception($"HTTP call GetToken failed with status code {response.StatusCode} and message {responseContent}");
}
}
private string GetAuthJwtToken(string scopes)
{
X509SecurityKey privateKey = new X509SecurityKey(this._clientCertificate);
ClaimsIdentity claimsIdentity = new ClaimsIdentity();
claimsIdentity.AddClaim(new Claim("sub", this._clientId));
claimsIdentity.AddClaim(new Claim("scope", scopes));
claimsIdentity.AddClaim(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
return this._tokenHandler.CreateEncodedJwt(this._clientId, this._authUrl, claimsIdentity, null, DateTime.UtcNow.AddMinutes(10), DateTime.UtcNow, new SigningCredentials(privateKey, SecurityAlgorithms.RsaSha256Signature));
}
}
}
@brz
Copy link

brz commented May 6, 2021

Dank voor dit stukje code! Ik zat even vast bij de message signing.

@fdervishi90
Copy link

fdervishi90 commented Jun 8, 2021

Can you please share how you instantiate the MagdaTokenBasedServicesExample class. More specifically how do you build the IRequestSignerFactory dependency on the contructor ?

@brz
Copy link

brz commented Jun 8, 2021

Can you please share how you instantiate the MagdaTokenBasedServicesExample class. More specifically how do you build the IRequestSignerFactory dependency on the contructor ?

Currently the only way of instantiating an IRequestSignerFactory object is by using dependency injection. I opened an issue on the GitHub page of Dalion.HttpMessageSigning to improve this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment