Created
March 17, 2026 19:39
-
-
Save joerodgers/7d5f87d5e4f53b032ce4e28fc9641adb to your computer and use it in GitHub Desktop.
Sample Power Platform custom connector script to reformat an access Entra token to support token signature validation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using Newtonsoft.Json; | |
| using System.Net; | |
| using System.Security.Cryptography; | |
| using System.Text; | |
| public class Script : ScriptBase | |
| { | |
| public override async Task<HttpResponseMessage> ExecuteAsync() | |
| { | |
| // Check if the operation ID matches what is specified in the OpenAPI definition of the connector | |
| if (string.Equals(this.Context.OperationId, "ReformatAccessForSignatureValidation", StringComparison.OrdinalIgnoreCase)) | |
| { | |
| return await HandleReformatAccessForSignatureValidationOperation().ConfigureAwait(false); | |
| } | |
| // Handle an invalid operation ID | |
| HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest); | |
| response.Content = CreateJsonContent($"Unknown operation ID '{this.Context.OperationId}'"); | |
| return response; | |
| } | |
| private async Task<HttpResponseMessage> HandleReformatAccessForSignatureValidationOperation() | |
| { | |
| try | |
| { | |
| var body = JsonConvert.DeserializeObject<IncomingRequestBody>(await Context.Request.Content.ReadAsStringAsync()); | |
| if (body == null || string.IsNullOrEmpty(body.AccessToken)) | |
| { | |
| return new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = CreateJsonContent(JsonConvert.SerializeObject(new { message = "error parsing body" })) }; | |
| } | |
| var jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); | |
| var jwtSecurityToken = jwtSecurityTokenHandler.ReadJwtToken(body.AccessToken); | |
| jwtSecurityToken.Header.nonce = ComputeSHA265Hash(jwtSecurityToken.Header.nonce); | |
| return new HttpResponseMessage(HttpStatusCode.OK) { Content = CreateJsonContent(JsonConvert.SerializeObject(new { accessToken = jwtSecurityTokenHandler.WriteJwtToken(jwtSecurityToken) })) }; | |
| } | |
| catch (System.Exception) | |
| { | |
| return new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = CreateJsonContent(JsonConvert.SerializeObject(new { message = "Error parsing body" })) }; | |
| } | |
| } | |
| private string ComputeSHA265Hash(string input) | |
| { | |
| using var sha256 = SHA256.Create(); | |
| byte[] bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input)); | |
| return Base64UrlEncoder.Encode(bytes); | |
| } | |
| public class IncomingRequestBody | |
| { | |
| [Newtonsoft.Json.JsonProperty(PropertyName = "accessToken")] | |
| public string AccessToken { get; set; } = string.Empty; | |
| } | |
| public class JwtSecurityTokenHandler | |
| { | |
| public JwtSecurityToken ReadJwtToken(string token) | |
| { | |
| return new JwtSecurityToken(token); | |
| } | |
| public string WriteJwtToken(JwtSecurityToken token) | |
| { | |
| return string.Format("{0}.{1}.{2}", token.EncodedHeader, token.EncodedPayload, token.RawSignature); | |
| } | |
| } | |
| public class JwtSecurityToken | |
| { | |
| private string accessToken = string.Empty; | |
| public JwtTokenHeader Header { get; set; } | |
| public string EncodedHeader | |
| { | |
| get | |
| { | |
| return Base64UrlEncoder.Encode(JsonConvert.SerializeObject(Header, Newtonsoft.Json.Formatting.None)); | |
| } | |
| } | |
| public string EncodedPayload { get; private set; } | |
| public string RawSignature { get; private set; } | |
| public JwtSecurityToken(string jwtEncodedString) | |
| { | |
| accessToken = jwtEncodedString; | |
| var parts = accessToken.Split('.'); | |
| if (parts.Length != 3) | |
| { | |
| throw new ArgumentException("Invalid JWT token format."); | |
| } | |
| Header = JsonConvert.DeserializeObject<JwtTokenHeader>(Base64UrlEncoder.Decode(parts[0])); | |
| if( Header == null) | |
| { | |
| throw new ArgumentException("Invalid JWT token format."); | |
| } | |
| EncodedPayload = parts[1]; | |
| RawSignature = parts[2]; | |
| } | |
| } | |
| public class JwtTokenHeader | |
| { | |
| [Newtonsoft.Json.JsonProperty(PropertyName = "typ")] | |
| public string typ { get; set; } | |
| [Newtonsoft.Json.JsonProperty(PropertyName = "nonce")] | |
| public string nonce { get; set; } | |
| [Newtonsoft.Json.JsonProperty(PropertyName = "alg")] | |
| public string alg { get; set; } | |
| [Newtonsoft.Json.JsonProperty(PropertyName = "x5t")] | |
| public string x5t { get; set; } | |
| [Newtonsoft.Json.JsonProperty(PropertyName = "kid")] | |
| public string kid { get; set; } | |
| } | |
| public static class Base64UrlEncoder | |
| { | |
| public static string Encode(byte[] bytes) | |
| { | |
| string base64 = Convert.ToBase64String(bytes); | |
| return base64.Replace('+', '-').Replace('/', '_').TrimEnd('='); | |
| } | |
| public static string Encode(string input) | |
| { | |
| string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(input)); | |
| return base64.Replace('+', '-').Replace('/', '_').TrimEnd('='); | |
| } | |
| public static string Decode(string input) | |
| { | |
| string base64 = input.Replace('-', '+').Replace('_', '/'); | |
| switch (base64.Length % 4) | |
| { | |
| case 2: base64 += "=="; break; | |
| case 3: base64 += "="; break; | |
| } | |
| byte[] bytes = Convert.FromBase64String(base64); | |
| return Encoding.UTF8.GetString(bytes); | |
| } | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment