5
votes

I am trying for a while to figure out how to solve SSO (Single Sign On) with Thinktecture IdentityServer v3 for a legacy webforms application. Unfortunately I am stacked.

The infrastructure is like this:

  • A WebForm App which need authentication and Authorization (possibly cookie or bearer token)
  • A javascript lightweight app (once the user is authenticated) makes requests to an WebApi (which is on separate domain)

I am having the following questions which hopefully will help me to bring things up:

  1. I can't make the legacy webforms application to redirect to IdentityServer, even with set in the Web.Config. I have in the Startup.cs the app.UseCookieAuthentication(....) and app.UseOpenIdConnectAuthentication(....) correctly set ( I guess ). For MVC the [Authorize] attribute force the redirection to the IdentityServer. How this should be done for webforms?
  2. Is there a way once the user is logged in, to reuse the token stored in the cookie as bearer token to the WebApi calls, made from the javascript client. I just want to do the requests to the WebApi on behalf on currently logged user (once again the webforms app and the webapi are on different domains)

Any help will be much appreciated.

Thanks!

1

1 Answers

4
votes

I'm currently working on the same type of project. This is what I have found out so far.

There is 4 Separate Concerns.

  1. Identity Server - Maintains Authenticating Users / Clients / Scope
  2. WebApi - Consumes Token generated by Identity Server for Authorization & Identity Information of User.
  3. WebForms / JQuery - For my project currently handles authentication for existing functionality redirects to the new WebApi.
  4. HTML using Javascript - Strictly uses WebApi for Information.

The custom grant below is for a user currently logged in through the WebForm as a membership object & I do not want to ask the user again to relogin via Identity Server.

For direct oAuth Authentication check out the sample here..

Sample Javascript Client

Configuring the Javascript an Implicit Flow would work just fine. Save the token connect with the api.

Identity Server v3

I had to configured using

Custom Grant w IUserService

Custom Grants

These will show how to configure a custom grant validation. With the user service you can have the Identity Service query existing users & customize claims.

There is alot of configuration to the Identity Server to make it your own. this is al very well documented on the IdentityServer website I wont go in how to set the basics up.

Ex: Client Configuration

 return new List<Client>
            {
                new Client
                {
                    ClientName = "Custom Grant Client",
                    Enabled = true,

                    ClientId = "client",
                    ClientSecrets = new List<ClientSecret>
                    {
                        new ClientSecret("secret".Sha256()),
                    },

                    Flow = Flows.Custom,
                    CustomGrantTypeRestrictions = new List<string>
                    {
                        "custom"
                    }
                }
            };

WebApi - Resource

Example WebApi Client Sample

Need to have the Nuget package

Thinktecture.IdentityServer.AccessTokenValidation

Startup.cs

app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
                {
                    //Location of your identity server
                    Authority = "https://localhost:44333/core"
                });

WebForms BackEnd WebForms Call

Need Nuget Package

Thinktecture.IdentityModel.Client

    [WebMethod]
    [ScriptMethod(ResponseFormat.Json)]
    public static string AuthorizeClient()
        {
                var client = new OAuth2Client(
                //location of identity server, ClientId, ClientSecret
                new Uri("http://localhost:44333/core/connect/token"),
                "client",
                "secret");
           //ClientGrantRestriction, Scope (I have a Client Scope of read), Listing of claims
            var result = client.RequestCustomGrantAsync("custom", "read", new Dictionary<string, string>
                {
                    { "account_store", "foo" },
                    { "legacy_id", "bob" },
                    { "legacy_secret", "bob" }
                }).Result;

           return result.AccessToken;
        }

These are generic claim for this example however I can generate my own claim objects relating to the user to send to the Identity Server & regenerate an Identity for the WebApi to consume.

WebForms / JQuery using

JQuery.cookie

 $('#btnTokenCreate').click(function (e) {

        //Create Token from User Information
        Ajax({
            url: "Default.aspx/AuthorizeClient",

            type: "POST"
        },
   null,
   function (data) {
       sendToken = data.d;

       //Clear Cookie
       $.removeCookie('UserAccessToken', { path: '/' });

       //Make API Wrap Info in Stringify
       $.cookie.json = true;
       //Save Token as Cookie
       $.cookie('UserAccessToken', sendToken, { expires: 7, path: '/' });

   });

JQuery WebAPI Ajax Sample Ajax Method - Note the beforeSend.

function Ajax(options, apiToken, successCallback) {
    //Perform Ajax Call
    $.ajax({
        url: options.url,
        data: options.params,
        dataType: "json",
        type: options.type,
        async: false,
        contentType: "application/json; charset=utf-8",
        dataFilter: function (data) { return data; },
        //Before Sending Ajax Perform Cursor Switch
        beforeSend: function (xhr) {
            //Adds ApiToken to Ajax Header 
            if (apiToken) {
                xhr.withCredentials = true;
                xhr.setRequestHeader("Authorization", " Bearer " + apiToken);
            }
        },
        // Sync Results
        success: function (data, textStatus, jqXHR) {
            successCallback(data, textStatus, jqXHR);
        },
        //Sync Fail Call back
        error: function (jqXHR, textStatus, errorThrown) {
            console.log(errorThrown);
        }

    });
}

AngularJS

This has same idea as the JQuery using the

module.run(function($http) {

  //Make API Wrap Info in Stringify
       $.cookie.json = true;
       //Save Token as Cookie
      var token = $.cookie('UserAccessToken');
$http.defaults.headers.common.Authorization = 'Bearer ' + token });

This makes the assumption your using the same domain as the WebForm. Otherwise I would use a Query string for a redirect to the Angular page with the token.

For CORS support need to make sure the WebApi has Cors configured for proper functionality. using the

Microsoft.AspNet.WebApi.Cors

Hope this sheds some light on the subject of how to approach this