Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJs ASP.NET WebApi Authentication with Thinktecture

I´m trying to make an AngularJs web that sends login and password to an ASP.NET WebApi backend and login this user with Thinktecture.

I have Thinktecture working fine with other project, ASP.NET MVC, using WS-Federation. Now I´m trying to do something similar but changing some components and I can´t make it work.

How can I send from ASP.NET WebApi the userName and password to Thinktecture and get it validated?

using System.Collections.Generic;
using System.IdentityModel.Services;
using System.Web.Http;
using WebApi_AngularJs.Model;

namespace WebApi_AngularJs.Controllers
{
    public class AuthorizationController : ApiController
    {    
        // POST: api/Authorization
        public LoginResponse Post([FromBody]Login data)
        {
            //HOW TO SEND data.user and data.password to ThinkTecture and get
            //response if user valid or not??
            var response = new LoginResponse { access_token = "token", data = "data"};
            return response;
        }   

    }
}

Thank you!

like image 546
cdiazal Avatar asked Jun 06 '14 08:06

cdiazal


2 Answers

There are a few things you need to do. Create an OAuth client that will make token requests, and use that to get access tokens from identity server allowing you to access your web api endpoints. To do this your OAuth client needs to have implicit flow enabled. You then make a login request to Identity server, typically through a pop up window to allow your OAuth client to log in. You need to pass in your OAuth client details in the query string of the login request to Idsrv, an OAuth client config would be what you defined in your Idsrv admin panel for the OAuth client, you would parameterize that and append it to the oauth2/authorzie url:

getIdpOauthEndpointUrl: function () {
                return "https://192.168.1.9/issue/oauth2/authorize";
},
getOAuthConfig: function () {
                return {
                    client_id: "Your Oauth CLient ID that you specifie din Identity Server",
                    scope: "The scope of your RP",
                    response_type: "token",
                    redirect_uri: "https://..YourAngularAppUrl/AuthCallback.html"
                };
}

You then open the login window:

var url = authService.getIdpOauthEndpointUrl() + "?" + $.param(authService.getOAuthConfig());
                    window.open(url, "Login", "height=500,width=350");

In your OAuth client inIdsrv you need to specify a redirect URL, in our case:

https://YourAngularAppUrl/AuthCallback.html

that is what you pass in to the the login request to identity server along with you OAuth client details. The AuthCallback.html page does nothing but extract the access token returned by idsrv to that page in a query string, and passes that into your angular app, how you do that is up to you, but that access token needs to be put into your $http headers.

The access token can be extracted in your AuthCallback.html page like this:

<script src="/Scripts/jquery-2.0.3.js"></script>
<script src="/Scripts/jquery.ba-bbq.min.js"></script>

<script type="text/javascript">
    var params = $.deparam.fragment(location.hash.substring(1));

    window.opener.oAuthCallback(params);
    window.close();
</script>

the OAuthCallback function is defined in my shell page, my index.html and is responsible for passing the token it's given into my angular app and putting it into the $http headers.

function oAuthCallback(OAUTHTOKEN) {
  angular.element(window.document).scope().setHttpAuthHeaderAndAuthenticate(OAUTHTOKEN);
}

The setHttpAuthHeaderAndAuthenticate() function is defined on my $rootScope, and it puts the token into the $http authorizaiton headers:

$http.defaults.headers.common.Authorization = 'Bearer ' + OAUTHTOKEN["access_token"];

Have a look at this post by Christian Weyer It does exactly what we're doing, but it's a knockout/web-api app, same concept still.

The next step is to tell your web api to accept the the access token from idsrv, this is simple;

public static void Configure(HttpConfiguration config)
        {
            var authConfig = new AuthenticationConfiguration();

            authConfig.AddJsonWebToken(
    YourIdsrvSiteId, YourRpsScope/Relam,YourRpsSymmetricSigningKey
);

            config.MessageHandlers.Add(new AuthenticationHandler(authNConfig));
        }

You could also define a ClaimsAuthenticationManager and a ClaimsAuthorizationManager here to allow you to transform claims and Grant/Deny acces sto the web api resources. Again this is all covered in Christian Weyer's post. Hope this helps.

like image 139
Mohammad Sepahvand Avatar answered Sep 28 '22 00:09

Mohammad Sepahvand


Finally, after reading a lot I have this:

In AngularJS:

'use strict';
app.factory('authService', ['$http', '$q', 'localStorageService', function ($http, $q, localStorageService) {

var serviceBase = 'http://localhost:64346/';
var authServiceFactory = {};

var _authData = localStorageService.get('authorizationData');

var _authentication = {
    isAuth: _authData != null? true : false,
    userName: _authData != null ? _authData.userName : ""
};

var _saveRegistration = function (registration) {

    _logOut();

    return $http.post(serviceBase + 'api/account/register', registration).then(function (response) {
        return response;
    });

};

var _login = function (loginData) {

    var data = "grant_type=password&username=" + loginData.userName + "&password=" + loginData.password;

    var deferred = $q.defer();

    $http.post(serviceBase + 'api/authorization', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).success(function (response) {

        localStorageService.set('authorizationData', { token: response.access_token, userName: loginData.userName });

        _authentication.isAuth = true;
        _authentication.userName = loginData.userName;

        deferred.resolve(response);

    }).error(function (err, status) {
        _logOut();
        deferred.reject(err);
    });

    return deferred.promise;

};

var _logOut = function () {

    $http.delete(serviceBase + 'api/authorization').success(function() {
        localStorageService.remove('authorizationData');

        _authentication.isAuth = false;
        _authentication.userName = "";
    });
};

var _fillAuthData = function () {

    var authData = localStorageService.get('authorizationData');
    if (authData) {
        _authentication.isAuth = true;
        _authentication.userName = authData.userName;
    }

}

authServiceFactory.saveRegistration = _saveRegistration;
authServiceFactory.login = _login;
authServiceFactory.logOut = _logOut;
authServiceFactory.fillAuthData = _fillAuthData;
authServiceFactory.authentication = _authentication;

return authServiceFactory;
}]);

In WebApi:

using System.Collections.Generic;
using System.Configuration;
using System.IdentityModel.Protocols.WSTrust;
using System.IdentityModel.Services;
using System.IdentityModel.Tokens;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Claims;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.Web.Http;
using System.Xml;
using Thinktecture.IdentityModel.Constants;
using Thinktecture.IdentityModel.WSTrust;
using WebApi_AngularJs.Model;

namespace WebApi_AngularJs.Controllers
{
    public class AuthorizationController : ApiController
    {
        // GET: api/Authorization
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET: api/Authorization/5
        [Authorize]
        public string Get(int id)
        {
            return "value";
        }

    // POST: api/Authorization
    public LoginResponse Post([FromBody]Login data)
    {
        var credentials = new ClientCredentials();
        credentials.UserName.UserName = data.UserName;
        credentials.UserName.Password = data.Password;

        ServicePointManager.ServerCertificateValidationCallback = (obj, certificate, chain, errors) => true;

        var claims = GetClaimsFromIdentityServer(data.UserName, data.Password);

        var response = new LoginResponse();
        if (claims != null)
        {
            //All set so now create a SessionSecurityToken
            var token = new SessionSecurityToken(claims)
            {
                IsReferenceMode = true  //this is 
                //important.this is how you say create 
                //the token in reference mode meaning 
                //your session cookie will contain only a 
                //referenceid(which is very small) and 
                //all claims will be stored on the server
            };
            FederatedAuthentication.WSFederationAuthenticationModule.
            SetPrincipalAndWriteSessionToken(token, true);

            response = new LoginResponse { access_token = token.Id , data = "data"};
        }

        return response;
    }

    // PUT: api/Authorization/5
    public void Put(int id, [FromBody]string value)
    {
    }

    // DELETE: api/Authorization/
    public void Delete()
    {
        //clear local cookie
        FederatedAuthentication.SessionAuthenticationModule.SignOut();
        FederatedAuthentication.SessionAuthenticationModule.DeleteSessionTokenCookie();
        FederatedAuthentication.WSFederationAuthenticationModule.SignOut(false);
    }

    private ClaimsPrincipal GetClaimsFromIdentityServer(string username, string password)
    {
        const string WS_TRUST_END_POINT = "https://srv:4443/issue/wstrust/mixed/username";
        var factory = new System.ServiceModel.Security.WSTrustChannelFactory
        (new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
                                     string.Format(WS_TRUST_END_POINT));
        factory.TrustVersion = TrustVersion.WSTrust13;
        factory.Credentials.UserName.UserName = username;
        factory.Credentials.UserName.Password = password;

        var rst = new System.IdentityModel.Protocols.WSTrust.RequestSecurityToken
        {
            RequestType = RequestTypes.Issue,
            KeyType = KeyTypes.Bearer,
            TokenType = TokenTypes.Saml2TokenProfile11,  
            AppliesTo = new EndpointReference
            ("urn:webapisecurity")
        };
        var st = factory.CreateChannel().Issue(rst);
        var token = st as GenericXmlSecurityToken;
        var handlers = FederatedAuthentication.FederationConfiguration.
        IdentityConfiguration.SecurityTokenHandlers;
        var token = handlers.ReadToken(new XmlTextReader
        (new StringReader(token.TokenXml.OuterXml))) as Saml2SecurityToken;
        var identity = handlers.ValidateToken(token).First();
        var principal = new ClaimsPrincipal(identity);
        return principal;
    }
}
}

In Web.Config:

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=301879
  -->
<configuration>
  <configSections>
    <section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
    <section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <appSettings>
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="ida:FederationMetadataLocation" value="https://srv:4443/FederationMetadata/2007-06/FederationMetadata.xml" />
    <add key="ida:Realm" value="urn:webapisecurity" />
    <add key="ida:AudienceUri" value="urn:webapisecurity" />
    <add key="AppName" value="Web API Security Sample" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.webServer>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="OPTIONSVerbHandler" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
    <modules>
      <add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
      <add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
    </modules>
    <validation validateIntegratedModeConfiguration="false" />
  </system.webServer>
  <system.identityModel>
    <identityConfiguration>
      <audienceUris>
        <add value="urn:webapisecurity" />
      </audienceUris>
      <claimsAuthorizationManager type="Thinktecture.IdentityServer.Ofi.AuthorizationManager, Thinktecture.IdentityServer.Ofi, Version=1.0.0.0, Culture=neutral" />
      <claimsAuthenticationManager type="Thinktecture.IdentityServer.Ofi.AuthenticationManager, Thinktecture.IdentityServer.Ofi, Version=1.0.0.0, Culture=neutral" />
      <certificateValidation certificateValidationMode="None" />
      <issuerNameRegistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <trustedIssuers>
          <add thumbprint="489116B0FCF14DF66D47AE272C3B9FD867D0E050" />
        </trustedIssuers>
      </issuerNameRegistry>
    </identityConfiguration>
  </system.identityModel>
  <system.identityModel.services>
    <federationConfiguration>
      <cookieHandler requireSsl="false" />
      <wsFederation passiveRedirectEnabled="true" issuer="https://srv:4443/issue/wsfed" realm="urn:webapisecurity" reply="http://localhost:64346/" requireHttps="false" />
    </federationConfiguration>
  </system.identityModel.services>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-5.1.0.0" newVersion="5.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.1.0.0" newVersion="5.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.1.0.0" newVersion="5.1.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
</configuration>

With this, I can see FedAuth cookie set in browser and it makes validation in WebApi too.

like image 39
cdiazal Avatar answered Sep 28 '22 02:09

cdiazal