Replacing CredentialsAuth with Okta sign-in widget

We would like to replace our web-based login page (which calls the ServiceStack auth service against a CredentialsAuthProvider) with the Okta sign-in widget. This will allow us to plug in two-factor authentication.

My web developer got the Okta sign-in widget working, and it returns a JSON object with a userid and a token - I am not sure how to create the correct ServiceStack auth code that parses the token, checks for validity, and then authenticates the user to the service.

1 Like

You will need to create a Custom AuthProvider. Have a look at one of the other OAuth Providers for examples which handle OAuth flow redirects and sets up a valid Auth User Session if they’re called back with a valid Access Token. The implementations for most Auth Providers are available in the /ServiceStack/auth folder.

E.g. the FacebookAuthProvider either Authenticates if it gets called back with a valid Server Auth Code it uses to request an AccessToken from Facebook and uses the AccessToken to download info about the User and calls base.OnAuthenticated to setup the Users Session and invoke the necessary Auth Events. This eventually calls LoadUserAuthInfo which Auth Providers can use to populate the User Session retrieved from each Auth Provider.

The alternative way to Authenticate is directly with a user-specified AccessToken which follows a similar code-path but skips the OAuth redirect flow used to retrieve the AccessToken and just authenticates directly.

Not sure what your Okta Custom AuthProvider will eventually end up looking like, but it needs to eventually call base.OnAuthenticated() and implement LoadUserAuthInfo() to populate the Users Session.

Got the basic concept above working - one remaining question - can my services have more than one subclass of CredentialsAuthProvider that is called based on the “provider” in the Authenticate object? It turns out that I’m not using OAuth, and the structure of the CredentialsAuthProvider will work well for my needs, but I already have one that will still be used in some cases.

I tried overridding the lower level ancestor AuthProvider but haven’t gotten it wired up correctly yet (LoadUserAuthInfo is not getting called).

You’ll need to specify a different Provider name for the CredentialsAuthProvider, only one of them can be called “credentials” and handle the default /auth/credentials requests.

@taglius I don’t suppose you could share more about how you got the Sign-in widget / TFA working with Credentials Auth aye?

The basics of my class are attached . The ConfigInfo class that is referenced is a bunch of statics out of my config file. The DVUser class is saved at the end and then reused in OnAuthenticated to fill the session properties.

Disclaimer: no idea if this is correct. I am now using something different that inherits of OAuthProvider, but I haven’t tested to see if it works with the Okta signin widget yet. My Okta stuff has yet to be used in production.

using System;
using System.Text;
using System.Collections.Generic;
using ServiceStack;
using ServiceStack.Text;
using ServiceStack.Configuration;
using ServiceStack.Auth;
using ServiceStack.Web;

//this was used by the login widget

namespace DV.Svc.Interface
{
    public class OktaAuthProvider : CredentialsAuthProvider
    {
        private DVUser usr;

        //how can I do these in order without having to repeat the string
        //there's a "Name" static but I don't think I can get to it
        public OktaAuthProvider(IAppSettings appSettings) : base(appSettings, Realm, "Okta")
        {
            Name = "Okta";
        }

        public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request)
        {
            bool bDenyAccess = false;

            //makes sure the fields coming from Okta match our values
            if (request.Meta.Get("iss") != ConfigInfo.OktaSite)
                bDenyAccess = true;

            if (request.Meta.Get("aud") != ConfigInfo.OktaClientId1)
                bDenyAccess = true;

            //parse the jwt token (middle section)
            string[] jwts = request.oauth_token.Split('.');
            if (jwts.Length != 3)
            {
                bDenyAccess = true;
            }
            else
            {
                var token = new StringBuilder(jwts[1]);
                while (token.Length % 4 > 0)
                    token.Append("=");

                byte[] buffer = Convert.FromBase64String(token.ToString());
                string json = Encoding.UTF8.GetString(buffer);

                dynamic dyn = DynamicJson.Deserialize(json);

                //fields inside the token should match also
                if (dyn.iss != ConfigInfo.OktaSite)
                    bDenyAccess = true;

                if (dyn.aud != ConfigInfo.OktaClientId1)
                    bDenyAccess = true;
            }

            usr = new DVUser()
            {
                UserID = request.UserName,
                Lastname = request.Meta.Get("family_name"),
                Firstname = request.Meta.Get("given_name"),
                Email = request.Meta.Get("email"),
                DenyAccess = bDenyAccess
            };

            request.Password = "xxx";       //dummy
            return base.Authenticate(authService, session, request);
        }

        public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
        {
            return !(usr.DenyAccess);  //returns true if authenticated
        }

        public override IHttpResult OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
        {
            session.IsAuthenticated = true;
            session.UserName = usr.UserID;
            session.UserAuthName = usr.UserID;
            session.FirstName = usr.Firstname;
            session.LastName = usr.Lastname;
            session.DisplayName = usr.Fullname;

            //fill other session stuff here (maybe from database?)

            //save this here, needed when system re-saves session
            ((DVAuthUserSession)session).Expiry = this.SessionExpiry;

            return base.OnAuthenticated(authService, session, tokens, authInfo);
        }

    }

}
1 Like