Skip to content
This repository has been archived by the owner on Dec 13, 2018. It is now read-only.

.net core api authentication using ws-federation #1918

Closed
MSAppsDev opened this issue Nov 20, 2018 · 7 comments
Closed

.net core api authentication using ws-federation #1918

MSAppsDev opened this issue Nov 20, 2018 · 7 comments

Comments

@MSAppsDev
Copy link

MSAppsDev commented Nov 20, 2018

My .net core api is calling on-Prem ADFS (WS-Federation sign-in protocol) for authentication but context.User is always null after successful login. Below is my Startup.cs

ConfigureServices()
{
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddWsFederation(options =>
{
options.Wtrealm = Configuration["wsfed:realm"];
options.MetadataAddress = Configuration["wsfed:metadata"];
options.SkipUnrecognizedRequests = true;
options.RequireHttpsMetadata = false;
})
.AddCookie();
}

Configure()
{
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseAuthentication();

        app.Use(async (context, next) =>
        {
            var user = context.User;
            if (user == null || !user.Identities.Any(identity => identity.IsAuthenticated))
            {
                await context.ChallengeAsync(WsFederationDefaults.AuthenticationScheme);
                return;
            }
            else
            {
                await next();
            }
        });
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller}/{action=Index}/{id?}");
        });
}

I see that IDP is redirecting to my api and returning wresult with the token value but when i try to access context.User, it is null.

Any pointers on whats wrong here?

@Tratcher
Copy link
Member

Can you share the Fiddler trace file?

Note WsFed is not appropriate for most API usage, JwtBearer is much better here.

@rasitha1
Copy link

@MSAppsDev fyi your code sample works just fine for me on ADFS 2016.

@MSAppsDev
Copy link
Author

@rasitha1, i am trying it on adfs 2012.

@MSAppsDev
Copy link
Author

MSAppsDev commented Nov 27, 2018

unfortunately i cannot use ad fs 2016, our Ops team setup ws-federation sign-in emitting jwt on ad fs 2012. Here is the response i am getting after entering credentials in login page.
<t:RequestSecurityTokenResponse xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
<t:Lifetime><wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2018-11-26T18:41:58.232Z</wsu:Created>
<wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2018-11-26T20:41:58.232Z</wsu:Expires></t:Lifetime>
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">wsa:Addressurn:adfs:federation:XXXX:Test</wsa:Address></wsa:EndpointReference></wsp:AppliesTo>
<t:RequestedSecurityToken>
<wsse:BinarySecurityToken wsu:Id="_62879890-6cee-4ca5-a679-8ef96c945f23-AF7CC0FC9150402EBC2B87449ECC9273" ValueType="urn:ietf:params:oauth:token-type:jwt"
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
ZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKU1V6STFOaUlzSW5nMWRDSTZJamN5Y0ZJMVMzZ3hTamxaYTBaRFRUTmlNbkpKT1ZGd04xbFVaeUo5LmV5SmhkV1FpT2lKMWNtNDZZV1JtY3pwbVpXUmxjbUYwYVc5dU9rRlVWRk02VkdWemRDSXNJbWx6Y3lJNkltaDBkSEE2THk5emMyOXNiMmRwYmkxemRHY3VjSFZpYkdsNExtTnZiUzloWkdaekwzTmxjblpwWTJWekwzUnlkWE4wSWl3aWFXRjBJam94TlRRek1qVTNOekU0TENKbGVIQWlPakUxTkRNeU5qUTVNVGdzSW5kcGJtRmpZMjkxYm5SdVlXMWxJam9pVUZWQ1RFbFlRVkJRVkZOVVhGeDVZWFIwYzNOd2N5SXNJblZ3YmlJNklubGhkSFJ6YzNCelFIQjFZbXhwZUdGd2NIUnpkQzVqYjIwaUxDSm5hWFpsYmw5dVlXMWxJam9pV1VGVVZGTlRVRk1pTENKbGJYQnNiM2xsWlVsRUlqb2lXVUZVVkZOVFVGTWlMQ0p6ZFdJaU9pSjVZWFIwYzNOd2MwQndkV0pzYVhoaGNIQjBjM1F1WTI5dElpd2ljbTlzWlNJNklsQnliMlJUZFhCd2IzSjBJaXdpWVhWMGFHMWxkR2h2WkNJNkluVnlianB2WVhOcGN6cHVZVzFsY3pwMFl6cFRRVTFNT2pJdU1EcGhZenBqYkdGemMyVnpPbEJoYzNOM2IzSmtVSEp2ZEdWamRHVmtWSEpoYm5Od2IzSjBJaXdpWVhWMGFGOTBhVzFsSWpvaU1qQXhPQzB4TVMweU5sUXhPRG8wTVRvMU5pNDNOREZhSWl3aWRtVnlJam9pTVM0d0luMC5ySk9WSnFlM3V1V2FtV0p6bk5sNVFfbGRhMnc2bGRtUUozZnNfRk9wM1dDSnpRVlVzbTdOSGR6T3dQZU5lWlFya3czajZDdGktVjlEbk1fT2JncEQxRUh1YXlTZ0R5TzZ5RU8xT0JDZnp4UUd1enRBNjNZVlFWd0czWnJvd3BnanV5WVBBVTZoMXMxZ1hnd2dBbUhtNTcweE91ZFk0Rzhxb1BIa08wZG95SHlUeUl6R2dZR0VhMFYxX0NYU0piV1RyWDBZMHduSmZLbEQzQmVTX1RRY1JMbUpvMktFbzhQYnBVV2g3bDlqdE9jNUplV0pKUUNjRldlOEUwOVltUjNITHJzMllvVUZJYllaWEFwZ2J6d3NXSWlMTFRLLXNBLUQtZzBQZFN2N2ozNjFVUDVBZWY5SEhQelRuWVJTeGYwRjluSUNHUm9kVUVYbjRWbUg1MGxkVkE=
</wsse:BinarySecurityToken>
</t:RequestedSecurityToken>
<t:TokenType>urn:ietf:params:oauth:token-type:jwt</t:TokenType>
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
</t:RequestSecurityTokenResponse>

Since claims are null, it gets into loop for 6 to 8 (whatever is configured in ad fs retry) then errors out.

@rasitha1
Copy link

Looks like WsFederationOptions is initialized with Saml and JWT token handlers however that's the wrong JWT token handler for this RequestedSecurityToken.

JwtSecurityTokenHandler expects a plain JWT string and not this BinarySecurityToken.

So in order to get this working you need to change ADFS (-EnableJWT $false) and get a regular SAML assertion. Then the sign-in should work.

Does anyone know what's the point in having JwtSecurityTokenHandler listed in WsFederationOptions? Is there ever a case where a WS-Fed RSTR can come with RequestedSecurityToken having a plain JWT?

It seems like a wrapper WsFedJwtSecurityTokenHandler is needed for handling urn:ietf:params:oauth:token-type:jwt tokens which will extract the base64 JWT from the BinarySecurityToken and then call the existing JwtSecurityTokenHandler passing in the actual JWT.

@Tratcher
Copy link
Member

@brentschmaltz

@MSAppsDev
Copy link
Author

MSAppsDev commented Nov 29, 2018

Finally made it work, here is the custom tokenvalidator if someone needs, this parses BinarySecurityToken from Base 64.

using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace XXXXX.Web
{
    /// <summary>
    /// Custom JWT security token validator
    /// </summary>
    public class WsFedTokenValidator : ISecurityTokenValidator
    {
        private string _jwtTokenString = null;
        private JwtSecurityToken _jwt = null;
        private JwtSecurityTokenHandler _jwtHandler = new JwtSecurityTokenHandler();

        /// <summary>
        /// Returns true if a token can be validated
        /// </summary>
        /// <returns>true if a token can be validated</returns>
        public bool CanValidateToken => throw new NotImplementedException();

        /// <summary>
        /// Gets and sets the maximum size in bytes that will be processed.
        /// </summary>
        /// <returns>token size</returns>
        public int MaximumTokenSizeInBytes { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }

        /// <summary>
        /// Read JWT token 
        /// </summary>
        /// <param name="securityToken"></param>
        /// <returns>tue in case of valid token else false</returns>
        public bool CanReadToken(string securityToken)
        {
            bool response = false;
            try
            {
                if (!string.IsNullOrWhiteSpace(securityToken))
                {
                    var xmlDoc = new System.Xml.XmlDocument();
                    xmlDoc.LoadXml(securityToken);
                    if (xmlDoc != null)
                    {
                        byte[] tokenBytes = Convert.FromBase64String(xmlDoc.InnerText); //convert the Base64-encoded binary to bytes
                        _jwtTokenString = Encoding.UTF8.GetString(tokenBytes); //get the original base64-encoded token
                        if (!string.IsNullOrWhiteSpace(_jwtTokenString))
                        {
                            _jwt = _jwtHandler.ReadToken(_jwtTokenString) as JwtSecurityToken;
                            if (_jwt != null)
                            {
                                response = true;
                            }
                        }
                    }
                }
                return response;
            }
            finally
            {

            }
        }

        /// <summary>
        /// Custom security token validation
        /// </summary>
        /// <param name="securityToken"></param>
        /// <param name="validationParameters"></param>
        /// <param name="validatedToken"></param>
        /// <returns>claims</returns>
        public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
        {
            ClaimsPrincipal principal = null;
            SecurityToken token = null;
            try
            {
                if (_jwt != null)
                {
                    principal = _jwtHandler.ValidateToken(_jwtTokenString, validationParameters, out token);
                }
                validatedToken = token;
                return principal;
            }
            finally
            {

            }
        }
    }
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants