Reputation: 2110
I'm using actionable messages (with Outlook web app) to call an Logic App. Therefore I am getting an Bearer token in the request:
"Action-Authorization": "Bearer eyJ0eXAi..."
Callstack:
Outlook web app -> Logic App -> my endpoint hosted in azure
Now I tried to validate the token with jwt.io but getting an Issue that the Signature is invalid.
So I tried to validate it in c# with the JwtSecurityTokenHandler
.
I tried to add https://substrate.office.com/sts/
to the issuer list, but it seems like the validation don't even get there.
I'm using the following code to validate the jwt token issued by office.com:
bool IsAuthorized(HttpActionContext actionContext)
{
var valid = base.IsAuthorized(actionContext);
// Custom handle for Bearer token, when invalid from base-class
if (!valid && actionContext.Request.Headers.Authorization.Scheme == "Bearer")
{
var jwt = actionContext.Request.Headers.Authorization.Parameter;
var th = new JwtSecurityTokenHandler();
var sjwt = th.ReadToken(jwt) as JwtSecurityToken;
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = false,
//IssuerSigningToken = sjwt,
ValidateActor = false,
ValidateAudience = false,
ValidateIssuer = true,
ValidateLifetime = true,
ValidIssuers = new[] { "https://substrate.office.com/sts/" },
ValidAudiences = new[] {"https://XXX.logic.azure.com"}
};
SecurityToken validatedToken;
try
{
th.ValidateToken(jwt, validationParameters, out validatedToken);
}
catch (Exception ex)
{
return false;
}
}
return valid;
}
Here is my JWT token:
I am getting the exception:
IDX10500: Signature validation failed. Unable to resolve SecurityKeyIdentifier: 'SecurityKeyIdentifier
(
IsReadOnly = False,
Count = 2,
Clause[0] = X509ThumbprintKeyIdentifierClause(Hash = 0x818...),
Clause[1] = System.IdentityModel.Tokens.NamedKeySecurityKeyIdentifierClause
)
', ...
Even though I set ValidateIssuerSigningKey = false
.
Is there a way to accept https://substrate.office.com/sts/
as a valid issuer?
Upvotes: 3
Views: 13126
Reputation: 525
If you're using an OAuth spec JWT then the key to validate the ticket can be retrieved via 'http://auth.myApplication.com/.well-known/jwks.json' and the built in JwtSecurityTokenHandler can be used even if you're using the older System.IdentityModel.Tokens.Jwt which does not have the calls natively built in.
OWIN Example:
public static class AppBuilderExtensions
{
/// <summary>
/// Use JWT Authentication.
/// </summary>
/// <param name="app">The <see cref="IAppBuilder"/> to use.</param>
/// <param name="audiences">The expected audiences. Will be validated in the token.</param>
/// <returns>The <see cref="IAppBuilder"/> instance.</returns>
public static IAppBuilder UseJwtTokenAuthentication(
this IAppBuilder app,
params string[] audiences)
{
var validationParameters = new TokenValidationParameters
{
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateActor = true,
ValidateIssuerSigningKey = true,
ValidAudiences = audiences,
ValidIssuer = Constants.Issuer,
IssuerSigningKeyResolver = Constants.GetSigningKey,
};
var tokenHandler = new JwtSecurityTokenHandler();
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = audiences,
TokenHandler = tokenHandler,
TokenValidationParameters = validationParameters,
});
return app;
}
}
public static class Constants
{
/// <summary>
/// The authentication issuer.
/// </summary>
public const string Issuer = "https://auth.myApplication.com/"; // custom domain for Auth0
private static readonly OpenIdConnectKeyResolver KeyResolver;
static Constants() { KeyResolver = new OpenIdConnectKeyResolver(Issuer, TimeSpan.FromHours(1), TimeSpan.FromSeconds(10)); }
/// <summary>
/// Gets the <see cref="IssuerSigningKeyResolver" /> delegate to provide to <see cref="JwtSecurityTokenHandler" />.
/// </summary>
public static IssuerSigningKeyResolver GetSigningKey => KeyResolver.GetSigningKey;
}
Direct call Example:
void ValidateToken(string authenticationToken)
{
var issuer = "https://auth.myApplication.com/"; // custom domain for Auth0
var audiences = new[]
{
"https://secure.myApplication.com/",
};
using (var signingKeyResolver = new OpenIdConnectKeyResolver(issuer, TimeSpan.Zero, TimeSpan.FromSeconds(10)))
{
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateActor = true,
ValidateIssuerSigningKey = true,
ValidAudiences = audiences,
ValidIssuer = issuer,
IssuerSigningKeyResolver = signingKeyResolver.GetSigningKey,
};
var principal = tokenHandler.ValidateToken(authenticationToken, validationParameters, out var securityToken);
if (principal == null || securityToken == null)
{
throw new UnauthorizedAccessException();
}
}
}
This requires a signing key resolver, the example is using the second option as that's what I needed:
It is baked into Microsoft.IdentityModel.Tokens (see example here: https://github.com/auth0/auth0-aspnet-owin/blob/master/src/Auth0.Owin.OpenIdConnectSigningKeyResolver/OpenIdConnectSigningKeyResolver.cs).
It is a little more work if using System.IdentityModel.Tokens.Jwt (see example here: https://github.com/NaosProject/Naos.Auth/blob/main/Naos.Auth.Recipes.Jwt/OpenIdConnectKeyResolver.cs) I wrote a piece of code to accomplish this which can be copied directly (MIT license) or installed as a mix-in using package 'Naos.Auth.Recipes.Jwt'.
Upvotes: 0
Reputation: 2110
The exception says that the "Signature validation failed".
To resolve this problem we can't just add the wanted valid issuer to ValidIssuers
, we need the to verify that the token is issued from the issuer itself.
Especially for this case with office.com being the issuer I found the expected key (JWK - JSON Web Key) here: https://substrate.office.com/sts/common/discovery/keys (also https://substrate.office.com/sts/common/.well-known/openid-configuration)
Here is the working code:
bool IsAuthorized(HttpActionContext actionContext)
{
var valid = base.IsAuthorized(actionContext);
// Custom handle for Bearer token, when invalid from base-class
if (!valid && actionContext.Request.Headers.Authorization.Scheme == "Bearer")
{
var jwt = actionContext.Request.Headers.Authorization.Parameter;
var th = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new JsonWebKey(GetJWK()),
ValidIssuers = new[] { "https://substrate.office.com/sts/" }
};
Microsoft.IdentityModel.Tokens.SecurityToken validatedToken;
try
{
var claims = th.ValidateToken(jwt, validationParameters, out validatedToken);
valid = true;
}
catch (Exception ex)
{
valid = false;
}
}
return valid;
}
// Get the token from configuration
private string GetJWK()
{
return ConfigurationManager.AppSettings["ida:jwks_json"];
}
In the appsettings I put the RSA key from the website for validating the token, it looks like:
{"kty":"RSA","use":"sig","kid":"gY...","x5t":"gY...","n":"2w...","e":"AQAB","x5c":["MII..."]}
Upvotes: 2