Search code examples
wifclaims-based-identitythinktecture-ident-modelthinktecture

Thinktecture BasicAuthenticationSecurityTokenHandler returning StatusCode 500, Internal Server Error


I have recently upgraded from the older Thinktecture IdentityModel to the latest IdentityModel that supports Web API 2.

The below code works with the previous nuget packages (and lesser webapi version hosting it), when I am using System.Web.Mvc v5.2.2.0 although it gets to the end of the ValidateToken function an error is received by the calling client {StatusCode: 500, ReasonPhrase: 'Internal Server Error'

public class BasicAuthSecurityTokenHandlerWithClaimsOutput : BasicAuthenticationSecurityTokenHandler
{
    public BasicAuthSecurityTokenHandlerWithClaimsOutput(ValidateNameWithClaims validateNameGetClaims)
        : base()
    {
        if (validateNameGetClaims == null)
        {
            throw new ArgumentNullException("ValidateNameGetClaims");
        }

        this.validateNameGetClaims = validateNameGetClaims;
    }

    protected readonly ValidateNameWithClaims validateNameGetClaims;

    public override ReadOnlyCollection<ClaimsIdentity> ValidateToken(SecurityToken token)
    {
        if (token == null)
        {
            throw new ArgumentNullException("token");
        }

        if (base.Configuration == null)
        {
            throw new InvalidOperationException("No Configuration set");
        }

        UserNameSecurityToken unToken = token as UserNameSecurityToken;
        if (unToken == null)
        {
            throw new ArgumentException("SecurityToken is not a UserNameSecurityToken");
        }

        Claim[] lookedUpClaims = null;

        try
        {
            if (this.validateNameGetClaims(unToken.UserName, unToken.Password, out lookedUpClaims) == false)
            {
                throw new SecurityTokenValidationException(unToken.UserName);
            }
        }
        catch (Exception e)
        {
            // log the exception
            throw new SecurityTokenValidationException(unToken.UserName);
        }

        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.Name, unToken.UserName),
            new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password),
            AuthenticationInstantClaim.Now
        };

        if(lookedUpClaims != null && lookedUpClaims.Length > 0)
            claims.AddRange(lookedUpClaims);

        if (RetainPassword)
        {
            claims.Add(new Claim("password", unToken.Password));
        }

        var identity = new ClaimsIdentity(claims, "Basic");

        if (Configuration.SaveBootstrapContext)
        {
            if (this.RetainPassword)
            {
                identity.BootstrapContext = new BootstrapContext(unToken, this);
            }
            else
            {
                var bootstrapToken = new UserNameSecurityToken(unToken.UserName, null);
                identity.BootstrapContext = new BootstrapContext(bootstrapToken, this);
            }
        }

        return new List<ClaimsIdentity> { identity }.AsReadOnly();
    }
}

The claims I am returning are:

[0] = {http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name: [email protected]}
[1] {http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod: http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password}
[2] {http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant: 2015-01-27T00:50:20.603Z}
[3] {fuid: 6}
[4] {fustate: FL}

I configure by:

public static class BasicAuthHandlerExtensionWithClaimsOutput
{
    public static void AddBasicAuthenticationWithClaimsOutput(
        this AuthenticationConfiguration configuration,
        ValidateNameWithClaims validationDelegate,
        string realm = "localhost", bool retainPassword = false)
    {
        var handler = new BasicAuthSecurityTokenHandlerWithClaimsOutput(validationDelegate)
        {
            RetainPassword = retainPassword
        };

        configuration.AddMapping(new AuthenticationOptionMapping
        {
            TokenHandler = new SecurityTokenHandlerCollection { handler },
            Options = AuthenticationOptions.ForAuthorizationHeader(scheme: "Basic"),
            Scheme = AuthenticationScheme.SchemeAndRealm("Basic", realm)
        });
    }
}

var authConfig = new AuthenticationConfiguration
{
    EnableSessionToken = true,
    SendWwwAuthenticateResponseHeaders = true,
    RequireSsl = false,

    SessionToken = new SessionTokenConfiguration
    {
        Audience = "http://audience.com,
        IssuerName = "http://issuer.com",
        EndpointAddress = appSettings.TokenEndPoint,
        SigningKey = appSettings.StsSigningKey,
        DefaultTokenLifetime = new TimeSpan(1, 0, 0)
    }
};

var userCredentialsService = new CredentialsService(credentialStore);
authConfig.AddBasicAuthenticationWithClaimsOutput(userCredentialsService.Validate);
config.MessageHandlers.Add(new AuthenticationHandler(authConfig));

Any idea what I am doing wrong please?


Solution

  • Use Fiddler and you will find Method not found: no match for ctor signature then a bit of searching will reveal this comment by the author of IdentityModel.

    The required signature exists in version 2.0.0.0 of System.IdentityModel.Tokens.Jwt but is no longer present in version 4.0.20622.1351

    You have to use Katana.