How to decode SessionSecurityToken - .net-4.5

Is it possible to decode a SessionSecurityToken?
I've set up a site to work with ThinkTecture IdentityServer using MachineKeySessionSecurityTokenHandler, and everything works as expected.
But now I need to pass the token to another service, but in an Authorization HTTP header instead of a cookie.
I've tried the following:
var cookie = HttpContext.Current.Request.Cookies[FederatedAuthentication.FederationConfiguration.CookieHandler.Name];
if (cookie != null)
{
var t = MachineKey.Unprotect(Convert.FromBase64String(cookie.Value), "System.IdentityModel.Services.MachineKeyTransform");
}
but this throws a System.Security.Cryptography.CryptographicException

Found the solution.
The only (easy) way of sending the data across the wire is to convert the SessionSecurityToken to a JwtSecurityToken and use the RawData property.
Sample implementation (dependent on ThinkTecture.IdentityModel):
public JwtSecurityToken ConvertSessionToJsonWebSecurityToken(SessionSecurityToken sessionToken)
{
var h = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.SecurityTokenHandlers[typeof(JwtSecurityToken)] as JwtSecurityTokenHandler;
if (h != null)
{
var issuer = ((ValidatingIssuerNameRegistry)h.Configuration.IssuerNameRegistry).IssuingAuthorities.First().Name;
var audience = h.Configuration.AudienceRestriction.AllowedAudienceUris.First().AbsoluteUri;
var signingKey = ((ValidatingIssuerNameRegistry)h.Configuration.IssuerNameRegistry).IssuingAuthorities.First().SymmetricKeys.First();
var securityKey = ((NamedKeyIssuerTokenResolver)h.Configuration.IssuerTokenResolver).SecurityKeys.First().Value.First();
// Create token
var t = h.CreateToken(
null,
null,
(ClaimsIdentity)sessionToken.ClaimsPrincipal.Identity,
new Lifetime(sessionToken.ValidFrom, sessionToken.ValidTo),
new SigningCredentials(
securityKey,
Algorithms.HmacSha256Signature,
Algorithms.Sha256Digest));
// Serialize token for validaiton
var s = h.WriteToken(t);
// Validate token
var validationParameters = new TokenValidationParameters()
{
AllowedAudience = audience,
ValidIssuer = issuer,
SigningToken = new BinarySecretSecurityToken(Convert.FromBase64String(signingKey))
};
h.ValidateToken(s, validationParameters);
// Return token with correct type
return h.ReadToken(s) as JwtSecurityToken;
}
return null;
}
[Test]
public void GetToken_WhenValidSessionTokenExist_ShouldReturnValidJwtToken()
{
JwtSecurityToken c;
FederatedAuthentication.SessionAuthenticationModule.TryReadJwtTokenFromCookie(container.GetInstance<ISecurityTokenOperations>(), out c)
Assert.That(!string.IsNullOrEmpty(c.RawData));
}

Related

JwtBearer middleware with ES256 always 401 Bearer error="invalid_token", error_description="The signature key was not found"

token is created using
public class AppTokenHandler : TokenValidator, IAppTokenHandler
{
private readonly JwtSecurityTokenHandler _handler = new JwtSecurityTokenHandler();
private readonly AppTokenConfiguration _appTokenConfiguration;
private readonly RsaSecurityKey _publicKey;
private readonly ECDsa _key;
public AppTokenHandler(IOptions<AppTokenConfiguration> appTokenConfiguration, RsaSecurityKey publicKey, ECDsa key)
{
_appTokenConfiguration = appTokenConfiguration.Value;
_publicKey = publicKey;
_key = key;
}
public string Create(Dictionary<string, object> claims)
{
var name = claims["name"].ToString();
////create token security key used to sign token from app's rsa private key
//using var rsa = RSA.Create();
//var rsaKey = _appTokenConfiguration.RsaKey;
//rsa.ImportRSAPrivateKey(Convert.FromBase64String(rsaKey), out _);
//RsaSecurityKey rsaSecurityKey = new(rsa);
////create signing credentials, specifying not to cache signature provider
//SigningCredentials signingCredentials = new(rsaSecurityKey, SecurityAlgorithms.RsaSha256)
//{
// CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false }
//};
SigningCredentials signingCredentials = new(new ECDsaSecurityKey(_key), SecurityAlgorithms.EcdsaSha256);
// create token
var tokenDescriptor = new SecurityTokenDescriptor
{
Audience = _appTokenConfiguration.Audience,
Claims = claims,
Expires = DateTime.UtcNow.AddDays(2),
IssuedAt = DateTime.UtcNow,
Issuer = _appTokenConfiguration.Issuer,
SigningCredentials = signingCredentials,
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, name),
})
};
var encodedJwt = _handler.CreateEncodedJwt(tokenDescriptor);
return encodedJwt;
}
public override bool Validate(string tokenString, out JwtSecurityToken token, out SecurityTokenValidationException validationException)
{
validationException = null;
token = null;
var publicKey = ECDsa.Create(_key.ExportParameters(false));
var validationParameters = new TokenValidationParameters
{
// validate lifetime
RequireExpirationTime = true,
ValidateLifetime = true,
// validate audience
RequireAudience = true,
ValidateAudience = true,
ValidAudience = _appTokenConfiguration.Audience,
// validate issuer
ValidateIssuer = true,
ValidIssuer = _appTokenConfiguration.Issuer,
// set source of name
NameClaimType = "name",
// validate signing key
RequireSignedTokens = true,
ValidateIssuerSigningKey = true,
//IssuerSigningKey = _publicKey
IssuerSigningKey = new ECDsaSecurityKey(publicKey)
};
try
{
var validate = _handler.ValidateToken(tokenString, validationParameters, out var validatedSecurityToken);
token = _handler.ReadJwtToken(tokenString);
}
catch (SecurityTokenValidationException ex)
{
validationException = ex;
return false;
}
catch
{
throw;
}
return true;
}
public Dictionary<string, object> MapClaims(JwtSecurityToken accessToken, JwtSecurityToken idToken)
{
List<string> claimKeys = new()
{
"name",
"preferred_username",
"oid",
"tid",
"azp",
"family_name",
"given_name",
"email"
};
var claims = accessToken?
.Claims
.Where(x=>claimKeys.Contains(x.Type))
.ToDictionary(x => x.Type, x => x.Value as object)
??
new Dictionary<string, object>();
var idTokenClaims = idToken
.Claims
.Where(x => claimKeys.Contains(x.Type))
.ToDictionary(x => x.Type, x => x.Value as object);
foreach (var claim in idTokenClaims.Where(x => !claims.ContainsKey(x.Key)))
claims.Add(claim.Key, claim.Value);
claims.Add("scp", "app_authorized_user");
return claims;
}
}
token is configured using
public class AppTokenOptions
{
public static Action<JwtBearerOptions> ConfigureToken(IServiceCollection services)
{
return options =>
{
var serviceProvider = services.BuildServiceProvider();
var authConfig = serviceProvider.GetRequiredService<IOptions<AppTokenConfiguration>>();
var publicKey = serviceProvider.GetRequiredService<RsaSecurityKey>();
var privkey = serviceProvider.GetRequiredService<ECDsa>();
//var key = ECDsa.Create(privkey.ExportParameters(false));
options.IncludeErrorDetails = true;
options.TokenValidationParameters = new TokenValidationParameters
{
// validate lifetime
RequireExpirationTime = true,
ValidateLifetime = true,
// validate audience
RequireAudience = true,
ValidateAudience = true,
ValidAudience = authConfig.Value.Audience,
// validate issuer
ValidateIssuer = true,
ValidIssuer = authConfig.Value.Issuer,
// set source of name
NameClaimType = "name",
// validate signing key
RequireSignedTokens = true,
ValidateIssuerSigningKey = true,
//IssuerSigningKey = publicKey
IssuerSigningKey = new ECDsaSecurityKey(ECDsa.Create(privkey.ExportParameters(false)))
};
options.Events = new JwtBearerEvents();
options.Events.OnTokenValidated = async context =>
{
(context.Principal?.Identity as ClaimsIdentity)?.AddClaim(new Claim("cpcb", "test"));
};
};
}
}
auth is added immediately in ConfigureServices of Startup.cs using
public static class AuthServicesRegistration
{
public static IServiceCollection ConfigureApplicationAuthServices(this IServiceCollection services, IConfiguration Configuration)
{
// get relevant config sections
IConfiguration appAuth= Configuration.GetSection("Auth:app");
IConfiguration aadIdTokenAuth = Configuration.GetSection("Auth:AADIdToken");
// create keys for app token
using (RSA rsa = RSA.Create(3072))
{
string rsaKey = Convert.ToBase64String(rsa.ExportRSAPrivateKey());
string rsaPublicKey = Convert.ToBase64String(rsa.ExportRSAPublicKey());
appAuth["RsaKey"] = rsaKey;
appAuth["RsaPublicKey"] = rsaPublicKey;
}
// bind auth configs
services.Configure<AppTokenConfiguration>(appAuth);
services.Configure<AzureAdIdTokenConfiguration>(aadIdTokenAuth);
// add public key instance as singleton
// so it can be used in .net's token validation middleware
// otherwise if just declared when defined token validation parameters
// the RSA instance will be prematurely disposed and you will get misleading 401s
services.AddSingleton(provider => {
RSA rsa = RSA.Create();
rsa.ImportRSAPublicKey(Convert.FromBase64String(appAuth["RsaPublicKey"]), out _);
return new RsaSecurityKey(rsa);
});
services.AddSingleton(provider =>
{
return ECDsa.Create(ECCurve.NamedCurves.nistP256);
});
// add provider for microsoft openidconnect config
services.AddSingleton<IOpenIdConnectConfigurationProvider>(provider =>
{
var stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
var configProvider = new OpenIdConnectConfigurationProvider(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
configProvider.AutomaticRefreshInterval = TimeSpan.FromHours(1);
return configProvider;
});
// add authentication schemes
// default is app token
services.AddAuthentication("app")
.AddJwtBearer("app", AppTokenOptions.ConfigureToken(services))
.AddMicrosoftIdentityWebApi(Configuration, "Auth:AzureAd", "aad");
// configure aad token options
services.Configure("aad", AzureAdTokenOptions.ConfigureAadToken());
// add authorization
services.AddAuthorization();
// add auth related services
services.AddScoped<IAppTokenHandler, AppTokenHandler>();
services.AddScoped<ITokenValidator, MicrosoftIdTokenValidator>();
return services;
}
}
token generation endpoint and test endpoint to validate token
[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
private readonly IAppTokenHandler _appTokenHandler;
private readonly ITokenValidator _idTokenValidator;
public AuthController(IAppTokenHandler tokenHandler, ITokenValidator idTokenValidator) : base()
{
_appTokenHandler = tokenHandler;
_idTokenValidator = idTokenValidator;
}
/// <summary>
/// Returns token for requested resource verifying using msal accesstoken
/// </summary>
/// <returns></returns>
[Authorize(AuthenticationSchemes = "aad")]
[RequiredScope(AcceptedScope = new[] { "app_login" })]
[Route("token")]
[HttpGet]
public async Task<IActionResult> token()
{
if (!Request.Headers.TryGetValue("identity", out var idTokenString)) return Unauthorized("No identity present to verify");
var aadToken = await HttpContext.GetTokenAsync("aad", "access_token");
JwtSecurityToken accessToken = null;
if (aadToken != null)
{
accessToken = new JwtSecurityToken(aadToken);
}
// verify idToken
if (!_idTokenValidator.Validate(idTokenString, out var idToken, out var validationException))
{
return Unauthorized($"invalid id token: {validationException.Message}");
}
// get / verify user
// get claims from tokens
var claims = _appTokenHandler.MapClaims(accessToken, idToken);
// generate token
var encodedJwt = _appTokenHandler.Create(claims);
return Ok(encodedJwt);
}
/// <summary>
/// Test authorize endpoint
/// </summary>
/// <returns></returns>
[Authorize]
[RequiredScope(AcceptedScope = new[] { "app_authorized_user" })]
[Route("validate/{token}")]
[HttpGet]
public async Task<IActionResult> validate(string token)
{
var authorization = Request.Headers.Authorization.ToString().Substring("Bearer ".Length).Trim();
try
{ // both validate calls are successful when authorize attribute is commented out
if (_appTokenHandler.Validate(token, out _, out var ex))
{
Debug.WriteLine("valid token");
}
else
{
Debug.WriteLine("invalid token", ex.Message);
}
if (_appTokenHandler.Validate(authorization, out _, out var ex2))
{
Debug.WriteLine("valid auth header");
}
else
{
Debug.WriteLine("invalid auth header", ex2.Message);
}
}
catch (Exception exc)
{
Debug.WriteLine(exc.Message);
}
return Ok(token);
}
}
if i swap out ES256 for RSA I had previously wired up, no issues. However, when I tried swapping out RSA for ES256, I got the error "The signature key was not found". If I remove the authorize attribute on the validate endpoint, get a token from the token endpoint and then verify the token using the app token handler, it's valid. There seems to be an issue with the jwt bearer middleware?? I have tried using the full ECDsa instead of just the public key, a singleton of an ECDsaSecurityKey with a key id and without a key id, a singleton of a jsonwebkey, and now a singleton of just the ecdsa. All same result. app token handler instance validates it with same token validation parameters, but jwt bearer middleware fails authorization. And again, if I swap out the signing credentials to use the RSA credentials, everything works just fine.
Am I creating the signing keys incorrectly?
Am I providing the public / private keys incorrectly to jwt bearer middleware?
Am I missing something in token creation?
How do I hook into jwt bearer middleware to get a better idea of what is going on? I see there is the ontokenvalidated event but that is not being hit obviously, is there an event I can hook into that might provide more info into what is going wrong?
Thanks!
I think the ecdsa instance used to create private key was getting displosed too early (?)
Ended up creating the ecdsa and saving the ecparams to config (similar as with rsa keys), created singleton of ecdsasecuritykey with just q and curve (so just public key), used serviceprovider to get ecdsasecuritykey to set as issuersigningkey for token validation in configure jwtbeareroptions, then used all ecparams from config (bound in apptokenconfiguration class) to create private key ecdsasecuritykey when creating the token
in configureapplicationauthservices
// create keys for app tokens
using (RSA rsa = RSA.Create(3072))
using (ECDsa ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256))
{
// rsa
string rsaKey = Convert.ToBase64String(rsa.ExportRSAPrivateKey());
string rsaPublicKey = Convert.ToBase64String(rsa.ExportRSAPublicKey());
appAuth["RsaKey"] = rsaKey;
appAuth["RsaPublicKey"] = rsaPublicKey;
appAuth["RsaKid"] = Guid.NewGuid().ToString();
// ecdsa
ECParameters ecParams = ecdsa.ExportParameters(true);
appAuth["D"] = Convert.ToBase64String(ecParams.D);
appAuth["QX"] = Convert.ToBase64String(ecParams.Q.X);
appAuth["QY"] = Convert.ToBase64String(ecParams.Q.Y);
appAuth["EcKid"] = Guid.NewGuid().ToString();
}
// bind auth configs
services.Configure<AppTokenConfiguration>(appAuth);
services.Configure<AzureAdIdTokenConfiguration>(aadIdTokenAuth);
// add rsa public key instance as singleton
// so it can be used in .net's token validation middleware
// otherwise if just declared when defined token validation parameters
// the RSA instance will be prematurely disposed and you will get misleading 401s
services.AddSingleton(provider => {
RSA rsa = RSA.Create();
rsa.ImportRSAPublicKey(Convert.FromBase64String(appAuth["RsaPublicKey"]), out _);
return new RsaSecurityKey(rsa);
});
// add ecdsa public key same way as rsa
services.AddSingleton(provider =>
{
ECParameters ecParams = new ECParameters();
ecParams.Curve = ECCurve.NamedCurves.nistP256;
ecParams.Q = new ECPoint()
{
X = Convert.FromBase64String(appAuth["QX"]),
Y = Convert.FromBase64String(appAuth["QY"])
};
ECDsa ecdsa = ECDsa.Create(ecParams);
return new ECDsaSecurityKey(ecdsa);
});
token configuration for middleware
public static Action<JwtBearerOptions> ConfigureToken(IServiceCollection services)
{
return options =>
{
var serviceProvider = services.BuildServiceProvider();
var authConfig = serviceProvider.GetRequiredService<IOptions<AppTokenConfiguration>>();
var publicKey = serviceProvider.GetRequiredService<RsaSecurityKey>();
var ecpublicKey = serviceProvider.GetRequiredService<ECDsaSecurityKey>();
options.IncludeErrorDetails = true;
options.TokenValidationParameters = new TokenValidationParameters
{
// validate lifetime
RequireExpirationTime = true,
ValidateLifetime = true,
// validate audience
RequireAudience = true,
ValidateAudience = true,
ValidAudience = authConfig.Value.Audience,
// validate issuer
ValidateIssuer = true,
ValidIssuer = authConfig.Value.Issuer,
// set source of name
NameClaimType = "name",
// validate signing key
RequireSignedTokens = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = ecpublicKey
//IssuerSigningKey = publicKey
};
options.Events = new JwtBearerEvents();
options.Events.OnTokenValidated = async context =>
{
(context.Principal?.Identity as ClaimsIdentity)?.AddClaim(new Claim("cpcb", "test"));
};
options.Validate();
};
}
token creation (and validation for manual testing)
public class AppTokenHandler : TokenValidator, IAppTokenHandler
{
//private readonly JsonWebTokenHandler _handler = new();
private readonly JwtSecurityTokenHandler _handler = new();
private readonly AppTokenConfiguration _appTokenConfiguration;
private readonly RsaSecurityKey _publicKey;
private readonly ECDsaSecurityKey _ecdsaPublicKey;
public AppTokenHandler(IOptions<AppTokenConfiguration> appTokenConfiguration, RsaSecurityKey publicKey, ECDsaSecurityKey ecdsaPublicKey)
{
_appTokenConfiguration = appTokenConfiguration.Value;
_publicKey = publicKey;
_ecdsaPublicKey = ecdsaPublicKey;
}
public string Create(Dictionary<string, object> claims)
{
var name = claims["name"].ToString();
//create token security key used to sign token from app's rsa private key
//using var rsa = RSA.Create();
//var rsaKey = _appTokenConfiguration.RsaKey;
//rsa.ImportRSAPrivateKey(Convert.FromBase64String(rsaKey), out _);
//RsaSecurityKey rsaSecurityKey = new(rsa);
////create signing credentials, specifying not to cache signature provider
//SigningCredentials signingCredentials = new(rsaSecurityKey, SecurityAlgorithms.RsaSha256)
//{
// CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false }
//};
ECParameters ecParams = new ECParameters();
ecParams.Curve = ECCurve.NamedCurves.nistP256;
ecParams.D = Convert.FromBase64String(_appTokenConfiguration.D);
ecParams.Q = new ECPoint()
{
X = Convert.FromBase64String(_appTokenConfiguration.QX),
Y = Convert.FromBase64String(_appTokenConfiguration.QY)
};
using ECDsa ecdsa = ECDsa.Create(ecParams);
ECDsaSecurityKey ecdsaSecurityKey = new(ecdsa);
SigningCredentials signingCredentials = new(ecdsaSecurityKey, SecurityAlgorithms.EcdsaSha256)
{
CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false }
};
// create token
var tokenDescriptor = new SecurityTokenDescriptor
{
Audience = _appTokenConfiguration.Audience,
Claims = claims,
Expires = DateTime.UtcNow.AddDays(2),
IssuedAt = DateTime.UtcNow,
Issuer = _appTokenConfiguration.Issuer,
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, name),
}),
SigningCredentials = signingCredentials
};
var encodedJwt = _handler.CreateEncodedJwt(tokenDescriptor);
return encodedJwt;
}
public override bool Validate(string tokenString, out JwtSecurityToken token, out SecurityTokenValidationException validationException)
{
validationException = null;
token = null;
var validationParameters = new TokenValidationParameters
{
// validate lifetime
RequireExpirationTime = true,
ValidateLifetime = true,
// validate audience
RequireAudience = true,
ValidateAudience = true,
ValidAudience = _appTokenConfiguration.Audience,
// validate issuer
ValidateIssuer = true,
ValidIssuer = _appTokenConfiguration.Issuer,
// set source of name
NameClaimType = "name",
// validate signing key
RequireSignedTokens = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = _ecdsaPublicKey
//IssuerSigningKey = _publicKey
};
try
{
var validate = _handler.ValidateToken(tokenString, validationParameters, out var validatedSecurityToken);
token = _handler.ReadJwtToken(tokenString);
}
catch (SecurityTokenValidationException ex)
{
validationException = ex;
return false;
}
catch
{
throw;
}
return true;
}
}
gonna clean this up a bit, but at least it is working correctly now :)

Xamarin Essentials Unable to exchange Okta authorization code for token

I was using OpenID and we have to switch to Xamarin.Essentials.WebAuthenticator.
I can get an authorization code from Okta using WebAuthenticator.AuthenticateAsync().
But, everything I try to then translate that code into an access token returns 400 Bad Request.
Okta's API error is "E0000021: HTTP media type not supported exception" and it goes on to say, "Bad request. Accept and/or Content-Type headers likely do not match supported values."
I have tried to follow https://developer.okta.com/blog/2020/07/31/xamarin-essentials-webauthenticator as much as possible, but we are not using the hybrid grant type like he is.
We are using only Authorization Code, which means I have to make a secondary call, and I have spent two days trying to figure out how.
private async Task LoginOktaAsync()
{
try
{
var loginUrl = new Uri(BuildAuthenticationUrl()); // that method is down below
var callbackUrl = new Uri("com.oktapreview.dev-999999:/callback"); // it's not really 999999
var authenticationResult = await Xamarin.Essentials.WebAuthenticator.AuthenticateAsync(loginUrl, callbackUrl);
string authCode;
authenticationResult.Properties.TryGetValue("code",out authCode);
// Everything works fine up to this point. I get the authorization code.
var url = $"https://dev-999999.oktapreview.com/oauth2/default/v1/token"
+"?grant_type=authorization_code"
+$"&code={authCode}&client_id={OktaConfiguration.ClientId}&code_verifier={codeVerifier}";
var request = new HttpRequestMessage(HttpMethod.Post, url);
var client = new HttpClient();
var response = await client.SendAsync(request); // this generates the 400 error.
}
catch(Exception e)
{
Debug.WriteLine($"Error: {e.Message}");
}
}
Here are the methods that produce the login url and a couple of other things:
public string BuildAuthenticationUrl()
{
var state = CreateCryptoGuid();
var nonce = CreateCryptoGuid();
CreateCodeChallenge();
var url = $"https://dev-999999.oktapreview.com/oauth2/default/v1/authorize?response_type=code"
+ "&response_mode=fragment"
+ "&scope=openid%20profile%20email"
+ "&redirect_uri=com.oktapreview.dev-999999:/callback"
+$"&client_id={OktaConfiguration.ClientId}"
+$"&state={state}"
+$"&code_challenge={codeChallenge}"
+ "&code_challenge_method=S256"
+$"&nonce={nonce}";
return url;
}
private string CreateCryptoGuid()
{
using (var generator = RandomNumberGenerator.Create())
{
var bytes = new byte[16];
generator.GetBytes(bytes);
return new Guid(bytes).ToString("N");
}
}
private string CreateCodeChallenge()
{
codeChallenge = GenerateCodeToVerify();
codeVerifier = codeChallenge;
using (var sha256 = SHA256.Create())
{
var codeChallengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeChallenge));
return Convert.ToBase64String(codeChallengeBytes);
}
}
private string GenerateCodeToVerify()
{
var str = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
Random rnd = new Random();
for (var i = 0; i < 100; i++)
{
str += possible.Substring(rnd.Next(0,possible.Length-1),1);
}
return str;
}
'''
After much online research, I discovered the issue was with how I was doing my post to get the token. This is how I made it work:
public static Dictionary<string, string> JsonDecode(string encodedString)
{
var inputs = new Dictionary<string, string>();
var json = JValue.Parse(encodedString) as JObject;
foreach (KeyValuePair<string, JToken> kv in json)
{
if (kv.Value is JValue v)
{
if (v.Type != JTokenType.String)
inputs[kv.Key] = v.ToString();
else
inputs[kv.Key] = (string)v;
}
}
return inputs;
}
private async Task<string> ExchangeAuthCodeForToken(string authCode)
{
string accessToken = string.Empty;
List<KeyValuePair<string, string>> kvdata = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", authCode),
new KeyValuePair<string, string>("redirect_uri", OktaConfiguration.Callback),
new KeyValuePair<string, string>("client_id", OktaConfiguration.ClientId),
new KeyValuePair<string, string>("code_verifier", codeVerifier)
};
var content = new FormUrlEncodedContent(kvdata);
var request = new HttpRequestMessage(HttpMethod.Post, OktaConfiguration.TokenUrl)
{Content = content, Method = HttpMethod.Post};
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.SendAsync(request);
string text = await response.Content.ReadAsStringAsync();
Dictionary<string, string> data = JsonDecode(text);
data.TryGetValue("access_token", out accessToken);
return accessToken;
}

How to Add auth_time claim in during jwt creation via help of SecurityTokenDescriptor and ClaimsIdentity

I am creating my jwt from following code. What i needed is auth_time claim which would be like something like
"nbf": 1582109929,
"exp": 1582110229,
"iat": 1582109929,
but I don't know how can I assign time value in above format or any way this claim automatically added with iat like value
public string GetIdTokenString(Dictionary<string, object> inputClaims, string secret)
{
string result = null;
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(secret);
List<Claim> claims = new List<Claim>();
foreach (var o in inputClaims)
{
string val = null;
if (o.Value != null)
{
Type t = o.Value.GetType();
bool isDict = t.IsGenericType /*&& t.GetGenericTypeDefinition() == typeof(Dictionary<,>)*/;
if (isDict)
{
val = JsonSerializer.Serialize(o.Value);
}
else
{
val = o.Value.ToString();
}
}
claims.Add(new Claim(o.Key, val));
}
claims.Add(new Claim("sub", Guid.NewGuid().ToString()));
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddSeconds(60 * 5),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
Audience = ObjInitialRequest.JwtData.ContainsKey("client_id") ? ObjInitialRequest.JwtData["client_id"] : null,
Issuer = "...",//from well known configuration issuer
};
var token = tokenHandler.CreateToken(tokenDescriptor);
if (token != null && token is JwtSecurityToken)
{
result = (token as JwtSecurityToken).RawData;
}
}
catch (Exception ex)
{
//Logger
}
return result;
}
I were able to add it like following
new Claim("auth_time", ((int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds).ToString(), ClaimValueTypes.Integer)
A short code
new Claim("auth_time", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(),ClaimValueTypes.Integer)

How to get SAML Response and SAML Token in Controller Action method

I am using this code in CALLback url using SUstainsys.SAaml2 library:
public ActionResult Callback()
{
var samlToken = (Saml2SecurityToken)null;
var samlResponses = HttpContext.Request.Form["SAMLResponse"]; HttpContext.Current.Request.Form["SAMLResponse"]; or elsewhere.
//if (form.Count() > 0)
//{
// var samlResponses = form.GetValues("SAMLResponse");
if (samlResponses != null)
{
foreach (var samlResponse in samlResponses)
{
try
{
var decodedSamlResponse = Convert.FromBase64String(samlResponse.ToString());
var reader = XmlReader.Create(new MemoryStream(decodedSamlResponse));
var serializer = new XmlSerializer(typeof(XmlElement));
var samlResponseElement = (XmlElement)serializer.Deserialize(reader);
var manager = new XmlNamespaceManager(samlResponseElement.OwnerDocument.NameTable);
manager.AddNamespace("saml2", "urn:oasis:names:tc:SAML:2.0:assertion");
var assertion = (XmlElement)samlResponseElement.SelectSingleNode("//saml2:Assertion", manager);
//var samltoken= Options.FromConfiguration.SPOptions.Saml2PSecurityTokenHandler.ReadToken(XmlReader.Create(new StringReader(assertion.OuterXml)));
samlToken = (Saml2SecurityToken)Options.FromConfiguration.SPOptions.Saml2PSecurityTokenHandler.ReadToken(XmlReader.Create(new StringReader(assertion.OuterXml)));
break;
}
catch { }
}
}
ViewBag.SamlResponse = samlResponses;
ViewBag.SamlToken = samlToken;
return View();
}
But I am getting null in SAML Response.
That functionality is built into the AcsCommand, you shouldn't do it yourself.
Look at the samples in the repo and use the HttpModule, Mvc Controller or Owin middleware (depending on what kind of application you have).

Azure Storage Service throws 403 forbidden error when trying to call REST API to clear queue messages

I'm trying to clear all azure storage queue message via Queue Service REST API. I've verified that the code is correct, but it still returns a 403 forbidden error. The "StorageSharedKey" and "StorageAccountName" are correct since I'm able to connect to the azure queue using those values in the connection string for the azure queue client. The storage version I'm using is "2015-12-11".
Here is the code:
internal void ClearStorageQueueMessages(string queueName)
{
const string requestMethod = "DELETE";
string urlPath = $"{queueName}/messages";
var dateInRfc1123Format = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);
var canonicalizedHeaders = $"x-ms-date:{dateInRfc1123Format}\nx-ms-version:{StorageVersion}";
var canonicalizedResource = $"/{StorageAccountName}/{urlPath}";
var uri = new Uri($"https://{StorageAccountName}.queue.core.windows.net/{urlPath}");
var response = MakeDeleteRestCall(uri, requestMethod, dateInRfc1123Format, canonicalizedHeaders, canonicalizedResource);
}
internal RestResponse MakeDeleteRestCall(Uri uri, string requestMethod, string dateInRfc1123Format, string canonicalizedHeaders,
string canonicalizedResource)
{
var restResponse = new RestResponse();
var stringToSign = $"{requestMethod}\n\n\n\n\n\n\n\n\n\n\n\n{canonicalizedHeaders}\n{canonicalizedResource}";
var authorizationHeader = CreateAuthorizationHeader(stringToSign);
var request = (HttpWebRequest) WebRequest.Create(uri);
request.Method = requestMethod;
request.Headers.Add("x-ms-date", dateInRfc1123Format);
request.Headers.Add("x-ms-version", StorageVersion);
request.Headers.Add("Authorization", authorizationHeader);
//request.Accept = "application/atom+xml,application/xml";
request.Accept = "application/json";
//request.ContentType = "application/json";
using (var response = (HttpWebResponse) request.GetResponse())
{
restResponse.StatusCode = response.StatusCode;
var responseStream = response.GetResponseStream();
if (responseStream == null)
return restResponse;
using (var reader = new StreamReader(responseStream))
{
restResponse.ReturnedContent = reader.ReadToEnd();
}
}
return restResponse;
}
internal static string CreateAuthorizationHeader(string canonicalizedString)
{
string signature;
using (var hmacSha256 = new HMACSHA256(Convert.FromBase64String(StorageSharedKey)))
{
var dataToHmac = Encoding.UTF8.GetBytes(canonicalizedString);
signature = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
}
var authorizationHeader = string.Format(CultureInfo.InvariantCulture, "{0} {1}:{2}", StorageSharedKey,
StorageAccountName, signature);
return authorizationHeader;
}
The problem seems to be with the header Authorization. Please check the format according to the documentation:
Authorization="[SharedKey|SharedKeyLite] :"
https://msdn.microsoft.com/en-us/library/azure/dd179428.aspx
Your function adds the shared key in plain text instead of the authorization scheme "SharedKey" or "SharedKeyLite".