Zoop
Zoop

Reputation: 882

How to implement ISecureDataFormat<AuthenticationTicket> Unprotect method?

Background:

I am attempting to support an authorization code flow to enable SSO from my app to a third party application.

Now I am stuck on the implementation of the Unprotect method which is supposed to return an AuthenticationTicket.

OAuth2 Server Configuration:

    var allowInsecureHttp = bool.Parse(ConfigurationManager.AppSettings["AllowInsecureHttp"]);

    var oAuthServerOptions = new OAuthAuthorizationServerOptions()
    {
        AllowInsecureHttp = allowInsecureHttp,
        TokenEndpointPath = new PathString("/oauth2/token"),
        AuthorizeEndpointPath = new PathString("/oauth2/authorize"),
        AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
        Provider = new CustomOAuthProvider(HlGlobals.Kernel),
        AccessTokenFormat = new CustomJwtFormat(_baseUrl, HlGlobals.Kernel),
        AuthorizationCodeProvider = new SimpleAuthenticationTokenProvider(),
        AuthorizationCodeFormat = new CustomJwtFormat(_baseUrl, HlGlobals.Kernel)
    };


    // OAuth 2.0 Bearer Access Token Generation
    app.UseOAuthAuthorizationServer(oAuthServerOptions);

JWT Token Generation / Protect method:

public string Protect(AuthenticationTicket data)
{
    if (data == null)
    {
        throw new ArgumentNullException("data");
    }

    // Get the client and assign to GUID -- the audience is api this token will be valid against
    string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
    Guid clientId;
    bool isValidAudience = Guid.TryParse(audienceId, out clientId);

    // Check for a valid Client Guid in the Auth ticket properties
    if (!isValidAudience)
    {
        throw new InvalidOperationException("AuthenticationTicket.Properties does not include audience");
    }

    // Create the JWT token
    string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
    var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
    var signingKey = new HmacSigningCredentials(keyByteArray);
    var issued = data.Properties.IssuedUtc;
    var expires = data.Properties.ExpiresUtc;
    var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
    var handler = new JwtSecurityTokenHandler();
    var jwt = handler.WriteToken(token);

    // Return the JWT Token
    return jwt;
}

Finally, the 'Unprotect' method which is responsible for validation of the JWT and returning and authentication ticket:

public AuthenticationTicket Unprotect(string protectedText)
{
    string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
    string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
    Guid clientId;
    bool isValidAudience = Guid.TryParse(audienceId, out clientId);

    var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
    var signingKey = new HmacSigningCredentials(keyByteArray);

    var tokenValidationParameters = new TokenValidationParameters
    {
        ValidAudience = audienceId,
        ValidIssuer = _issuer,
        IssuerSigningKey = signingKey // Cannot convert HMAC Signing Credentials to System.IdentityModel.Tokens.SecurityKey
        ValidateLifetime = true,
        ValidateAudience = true,
        ValidateIssuer = true,
        RequireSignedTokens = true,
        RequireExpirationTime = true,
        ValidateIssuerSigningKey = true
    };


    var handler = new JwtSecurityTokenHandler();
    SecurityToken token = null;
    var principal = handler.ValidateToken(protectedText, tokenValidationParameters, out token);
    var identity = principal.Identities;

    return new AuthenticationTicket(identity.First(), new AuthenticationProperties());
}

One issue right off the jump is the issuer signing key. I am having trouble coming up with an acceptable parameter. I am seeing the error message:

Cannot convert HMAC Signing Credentials to System.IdentityModel.Tokens.SecurityKey

To be honest, I am unsure why the Protect method needs to fire. I thought the flow would end with the returning of the JWT token, but apparently not. Now I am struggling with the implementation of the Unprotect method as it is something I have never had to struggle with previously.

Even if I set all of the options to 'false' on the tokenValidationParamters I am still getting the following error on validation:

An exception of type 'System.IdentityModel.SignatureVerificationFailedException' occurred in System.IdentityModel.Tokens.Jwt.dll but was not handled in user code

Additional information: IDX10503: Signature validation failed. Keys tried: ''.

Exceptions caught:

''.

token: '{"typ":"JWT","alg":"HS256"}.{"iss":"http://myissuer.com","aud":"346e886acabf457cb9f721f615ff996c","exp":1510925372,"nbf":1510925072}'

When I compare the values to the decrypted token using JWT.IO all of the values match as expected.

Any guidance on what I may be doing wrong here, or how to call validateToken with a valid signing key on the tokenvalidationparamters would be most helpful and appreciated.

Thanks in advance!

EDIT:

I am curious why the Unprotect is firing at all... I thought I should be returning a JWT token to the client. The method to return a JWT fires before the Unprotect method and the JWT is never returned to the client.

Do I have something configured incorrectly?

Upvotes: 4

Views: 5910

Answers (2)

Daniel
Daniel

Reputation: 598

In your Startup.cs file call the following methods. The Protect method in your customJwtFormat is called when the user actually tries to sign in to the authentication server endpoint. The unprotect method is called when the user tries to access a protected api url via the "Bearer [token]" authentication model. If you don't say for example app.UseOAuthBearerAuthentication in your Startup.cs file you'll end up getting errors such as

Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationMiddleware Warning: 0 : invalid bearer token received

 app.UseOAuthAuthorizationServer(OAuthServerOptions);
 app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
            {
                AccessTokenFormat = _tokenFormat
            });

Create your customjwtFormat like below, you can alter the implementation as required.

    public string Protect(AuthenticationTicket data)
    {
        if (data == null)
        {
            throw new ArgumentNullException("data");
        }

        string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];

        string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
        var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);

        //var signingKey = new HmacSigningCredentials(keyByteArray);
        var securityKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(keyByteArray);
        securityKey.KeyId = ConfigurationManager.AppSettings["as:AudienceId"];

        var signingCredentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);

        var issued = data.Properties.IssuedUtc;

        var expires = data.Properties.ExpiresUtc;

        var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingCredentials);

        var handler = new JwtSecurityTokenHandler();

        var jwt = handler.WriteToken(token);
        return jwt;
    }

    public AuthenticationTicket Unprotect(string protectedText)
    {
        string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
        string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
        string authority = ConfigurationManager.AppSettings["Authority"];

        var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
        var signingKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(keyByteArray);

        var tokenValidationParameters = new TokenValidationParameters
        {
            ValidAudience = audienceId,
            ValidIssuer = _issuer,
            IssuerSigningKey = signingKey,
            ValidateLifetime = true,
            ValidateAudience = true,
            ValidateIssuer = true,
            RequireSignedTokens = true,
            RequireExpirationTime = true,
            ValidateIssuerSigningKey = true
        };


        var handler = new JwtSecurityTokenHandler();
        SecurityToken token = null;

        // Unpack token
        var pt = handler.ReadJwtToken(protectedText);
        string t = pt.RawData;

        var principal = handler.ValidateToken(t, tokenValidationParameters, out token);

        var identity = principal.Identities;

        return new AuthenticationTicket(identity.First(), new AuthenticationProperties());
    }

Upvotes: 3

Marcelo Oviedo
Marcelo Oviedo

Reputation: 1

you should use something like this

 public AuthenticationTicket Unprotect(string protectedText)
    {


        if (string.IsNullOrWhiteSpace(protectedText))
        {
            throw new ArgumentNullException("protectedText");
        }
        string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
        string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
        var handler = new JwtSecurityTokenHandler();
        var token = handler.ReadToken(protectedText);
        SecurityToken validatedToken;
        string t = ((JwtSecurityToken)token).RawData;

        var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
        //var signingKey = new HmacSigningCredentials(keyByteArray);

        var validationParams = new TokenValidationParameters()
        {
            ValidateLifetime = false,
            ValidAudience = audienceId,
            ValidIssuer = audienceId,
            ValidateIssuer = false,
            ValidateAudience = false,
            IssuerSigningToken = new BinarySecretSecurityToken(keyByteArray)
        };
        var principal = handler.ValidateToken(t, validationParams, out validatedToken);
        var identity = principal.Identities;

        return new AuthenticationTicket(identity.First(), new AuthenticationProperties());

    }

Upvotes: 0

Related Questions