Open Closed

OpenIdConnect using okta widget #5763


1
cmthomps created

I'm trying to implement openid with okta using the okta widget. The documentation can be found here:

https://developer.okta.com/code/javascript/okta_sign-in_widget

I've got the widget rendering and I've modified the AccountController Login Method to return a Challenge Response.

if (!HttpContext.User.Identity.IsAuthenticated) { var properties = new AuthenticationProperties(); properties.Items.Add("sessionToken", sessionToken); properties.RedirectUri = "/app/dashboard"; return Challenge(properties, OktaDefaults.MvcAuthenticationScheme); } return RedirectToAction("Index", "Home");

I did this based on the okta example at https://github.com/oktadeveloper/okta-aspnetcore-mvc-example.

The okta sample works. But with my aspnetzero test project, I get a cors error after the challenge response.

Failed to load https://dev-586182.oktapreview.com/oauth2/default/v1/authorize?client_id=0oagaur4ukBFH91Od0h7&redirect_uri=http%3A%2F%2Flocalhost%3A62114%2Fauthorization-code%2Fcallback&response_type=code&scope=openid%20profile%20email&response_mode=form_post&nonce=636749499861104637.OWVjOGM2MzItODVhNS00MWU5LWI2NDQtZmE3YWEzMGMwZjIyZmQ2ODQwNTAtYjNiOC00ZTkwLThlYWYtOWFlNGRjOGE3YmIw&sessionToken=20111HIi49zni2fMgl9HsggpGhLnukMlTvIK2gVVWL_3c6bK7Ijq0e3&state=CfDJ8LRmRAoWNcxFrJRw5HHQysQzWhO-9Kwx2z8FNwUZylHUEde9SLy_fcsk3YAUaFtO4Maw_FxyHaTnpyc-HbXgmhjZwXgD5J8krSwBJWR83XmqGngBVCQqfboIKo5SmrFjv8g-tKqPFpwRKuSlD6yEWU2Q8lflrLrM5J5OAQep-beiBQqqaUThPKFdAw3v2w9MfRs95rXE4QUHFLrnw3L2SqLCzESVXpa3xiL_NwyhgsG7l69Anb-kTONGrtCREhbAljxfdPRosd1H0BQWbWsgoSlPeDwBDAdldkMntzJyTYtRtUw31jJRyKZCBlY_xkOXGqQNHIbJbqbCKGjgD3lh2B1saAbjvFwKoNxfi-CJxxBeoEEqoO24BFAGRN4Yl095VUeXHBEu0uCJ1RHyn3a4VsAOO0TsmeE1nqTEZuKHSpks&x-client-SKU=ID_NET451&x-client-ver=5.2.0.0: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:62114' is therefore not allowed access.

When I look at Fiddler to see what the differences are between the two, I see the following:

Okta Sample:

GET https://dev\-586182\.oktapreview\.com/oauth2/default/v1/authorize?client\_id=0oagavcxawHIwhL0v0h7&redirect\_uri=http%3A%2F%2Flocalhost%3A50336%2Fauthorization\-code%2Fcallback&response\_type=code&scope=openid%20profile%20email&response\_mode=form\_post&nonce=636749485315680253\.YWI5NmM0OTYtNmI5ZS00ZDdhLWI3YmQtZGRlODEyZDhlYTg1MmI3MWI0NDAtMzY5Ny00MmRkLTk0NTItN2NjNGM3MDk3OWMy&sessionToken=20111TZBaJj2UhRVDGyRC1ibEgwfPHi7BpH1FtjrqQdWKgQQcWeQAa5&state=CfDJ8LRmRAoWNcxFrJRw5HHQysT\_mCxdITM8LSpdFcyQylyYlrvf\_IULJTU1bxCNDrA4fwJTYirtf3BbBfx4hqb6R3nfp6aZLY6LltGFrllXW8AxP8oGhPSLZt\_dphp9MLFgnEiN2YbJwfoBfsclWolORc5l7o673dMOtOZm430zzDVQNybrio5Xl4e9NZKOCQ2UGvVR3T5T7NwbNG9jLbV\-Wl15qeh6tDZJvtv6yC8glj9SnIqMwDpa\-mGRE02NUG5UN8omJHDkqKu8xE8Da3P8lbeIZjTYKkEActLAmgZB1ZrtT6F\_e\_Jo2HJBmzZ61eP\-Nisp0cNiX7wGs\_axa5fjwR5KWyC4EWFyEPoDvDivFi46y9tz8citEr7u8F8K\-XoUqO\_pit1Tb6bDeGCr9peXXkxH5\_El9jYXUWq\_A4ZnEHkf&x\-client\-SKU=ID\_NETSTANDARD1\_4&x\-client\-ver=5\.2\.0\.0 200 OK (text/html)

AspNetZero Test:

OPTIONS https://dev\-586182\.oktapreview\.com/oauth2/default/v1/authorize?client\_id=0oagaur4ukBFH91Od0h7&redirect\_uri=http%3A%2F%2Flocalhost%3A62114%2Fauthorization\-code%2Fcallback&response\_type=code&scope=openid%20profile%20email&response\_mode=form\_post&nonce=636749484127536656\.NDc5OWM0MjAtNDExNy00NzAxLThkY2QtYjgyMTUyNDU4NDE0YzAyZDk5MzAtNTM3ZC00MDdiLThjYjctMjViYWNiNmRjYzVh&sessionToken=201115ITj3rXCMp473fVmULJCyQ4rpT3quS7wFqm9CYFVjSsWcMdLnH&state=CfDJ8LRmRAoWNcxFrJRw5HHQysSOL69ljb\_XmubbdQRu6u5yvUlhVRVcs9GnQDv4GZzwhPviVtZcBZWfu5cu746suqp2hGeW4wMy9qUzH9lIpzQaR3fHe4h9jRLCmS4YG48VUy5jBcCaz7oOhmRBD2FqmqyMcd3\_3gDRwU89UO9Acd5WEI\-0CQ\_mHy0GX24a9iVXNnCqftdPlPg8t\-4x501UiOtIfTo7J0HRk5cI\-WkX5R\_gJa5rp5lHG\-gMlQeEmKpBpBOmGFrEGBAJui0I\-RercQP0rAnFYg2s\_P5Oa1VrAC0U1sJ3TbMrbA9VYLBkiUM7K7rzueYh7os6uTsUeMDVv4qV3\_67oQM1BmjbKyzL7hw2iLSjVcuaaS5BbgxWyb\_QvgouezEHTsSbZcac0X1HrMu\-CZ01YnUMlaMfdJMHzbum&x\-client\-SKU=ID\_NET451&x\-client\-ver=5\.2\.0\.0 200 OK ()

I think the difference is that for some reason the Okta sample results in a get whereas the AspNetZero response results in an Options that I think is then failing because of cors.

Any thoughts? I know this one is tricky. I can send you both projects so that you can try it yourself.

Thanks, Craig


14 Answer(s)
  • 0
    maharatha created

    +1

    I have a madate to use Okta for authentication. @cmthomps can you share your code and also can you let me know how are you managing users and assigning them Tenants using Okta

  • 0
    ismcagdas created

    Hi @cmthomps

    I think you need to configure your org’s Trusted Origins to include url of your AspNet Zero app, see https://developer.okta.com/code/javascript/okta_sign-in_widget#enabling-cross-origin-access

  • 0
    cmthomps created

    Thanks @ismcagdas,

    Can I send you my source code to take a look at? I've set-up the cors stuff. It's literally driving me crazy. I have a test project that I am trying to use as a proof of concept. What is the best way to do that?

  • 0
    ismcagdas created

    @cmthomps

    Just to be sure, have you configured it on okta side ?

  • 0
    cmthomps created

    @ismcagdas - Yes, I've configured the application in Okta. I have an okta asp.net core example project that works. And, I have an AspNetZero proof of concept project that I have modified. I'm stuck at the moment.

    For the purposes of this proof of concept, I've modified the aspnetzero login page to render the Okta widget and I've modified the the account controller to handle the postback in the same way the okta example works.

  • 0
    ismcagdas created

    Could you check this https://support.aspnetzero.com/QA/Questions/5773#answer-e8770448-1af0-3901-c258-39e98c3b7cd7 ?

    If it doesn't help, please send your project to info@aspentzero.com with the necessary information and we will check it for you.

    Thanks,

  • 1
    cmthomps created

    I'm getting close.

    You're saying replace GetUserInfo in the method below with the method in the ticket you mentioned. The new method takes a "token" parameter. Where does that come from?

    private async Task<ExternalAuthUserInfo> GetExternalUserInfo(ExternalAuthenticateModel model)
            {
                var userInfo = await _externalAuthManager.GetUserInfo(model.AuthProvider, model.ProviderAccessCode);
                if (userInfo.ProviderKey != model.ProviderKey)
                {
                    throw new UserFriendlyException(L("CouldNotValidateExternalUser"));
                }
    
                return userInfo;
            }
    
  • 0
    maharatha created

    @cmthomps I am currently trying to add custom claims to make the existing code work but I am struggling to get it right. If in the meantime you are able to use the custom code provided then let me know if it works.

  • 0
    ismcagdas created

    Hi @cmthomps

    okta login page should redirect you back to ExternalAuthenticate action of TokenAuthController with the token parameter added to url.

  • 2
    cmthomps created

    I finally figured this out. Just to be clear, I am replacing the default AspNetZero login experience with the Okta sign-in widget. (See screenshot). In addition to making changes to the login screen itself, I had to make the changes to the AuthConfigurer.cs and the AccountController.cs

    The change to the AccountController was basic. Just made a new method to handle the postback from the Okta Widget:

        [HttpPost]
        public ActionResult ExternalLoginOkta(string sessionToken = "")
        {
            string provider = "OpenIdConnect";
            var redirectUrl = Url.Action(
                "ExternalLoginCallback",
                "Account",
                new
                {
                    ReturnUrl = "/App",
                    authSchema = provider,
                    ss = ""
                });
    
            var properties = new AuthenticationProperties();
            //Note: the sessionToken is created by the Okta Widget and passed into this method.
            properties.Items.Add("sessionToken", sessionToken);
            properties.RedirectUri = redirectUrl;
    
            var challengeResponse = Challenge(properties, OktaDefaults.MvcAuthenticationScheme);
            return challengeResponse;
        }
    

    I had to do some surgery on the AuthConfigurer.cs class.  I added some event handler to OpenId to handle some apparent data either being in the incorrect place or missing from the data coming back from Okta.

        public static void Configure(IServiceCollection services, IConfiguration configuration)
        {
            var authenticationBuilder = services.AddAuthentication();
    
            //if (bool.Parse(configuration["Authentication:OpenId:IsEnabled"]))
            //{
            //    authenticationBuilder.AddOpenIdConnect(options =>
            //    {
            //        options.ClientId = configuration["Authentication:OpenId:ClientId"];
            //        options.Authority = configuration["Authentication:OpenId:Authority"];
            //        options.SignedOutRedirectUri = configuration["App:WebSiteRootAddress"] + "Account/Logout";
            //        options.ResponseType = OpenIdConnectResponseType.IdToken;
    
            //        var clientSecret = configuration["Authentication:OpenId:ClientSecret"];
            //        if (!clientSecret.IsNullOrEmpty())
            //        {
            //            options.ClientSecret = clientSecret;
            //        }
            //    });
            //}
    
            if (bool.Parse(configuration["Authentication:OpenId:IsEnabled"]))
            {
    
                authenticationBuilder.AddOpenIdConnect(oidcOptions =>
                {
    
    
    
                    oidcOptions.ClientId = configuration["Authentication:OpenId:ClientId"];
                    oidcOptions.Authority = configuration["Authentication:OpenId:Authority"];
                    oidcOptions.SignedOutRedirectUri = configuration["App:WebSiteRootAddress"] + "Account/Logout";
                    oidcOptions.ResponseType = OpenIdConnectResponseType.Code;
                    oidcOptions.GetClaimsFromUserInfoEndpoint = true;
    
    
                    oidcOptions.Scope.Add("openid");
                    oidcOptions.Scope.Add("profile");
                    oidcOptions.Scope.Add("email");
    
                    var clientSecret = configuration["Authentication:OpenId:ClientSecret"];
                    if (!clientSecret.IsNullOrEmpty())
                    {
                        oidcOptions.ClientSecret = clientSecret;
                    }
    
    
                    oidcOptions.Events = new OpenIdConnectEvents()
                    {
                        //TODO This was the trick to get the Okta widget to work.
                        OnRedirectToIdentityProvider = context =>
                        {
                            // Add Okta sessionToken to provide custom login
                            if (context.Properties.Items.TryGetValue("sessionToken", out var sessionToken))
                            {
                                if (!string.IsNullOrEmpty(sessionToken))
                                {
                                    context.ProtocolMessage.SetParameter("sessionToken", sessionToken);
                                }
                            }
                            return Task.CompletedTask;
                        },
                        //TODO This is the only way i can get OpenId to work.
                        OnTicketReceived = context =>
                        {
                            if(!context.Properties.Items.ContainsKey("LoginProvider"))
                            {
                                //For some reason, the LoginProvider isn't being returned when using the Okta Widget
                                context.Properties.Items.Add("LoginProvider", "OpenIdConnect");
                            }
                            // Get the ClaimsIdentity
                            var identity = context.Principal.Identity as ClaimsIdentity;
    
                            if (identity != null)
                            {
                                // Add the Name ClaimType. This is required if we want User.Identity.Name to actually return something!
                                    if (!context.Principal.HasClaim(c => c.Type == ClaimTypes.Name) &&
                                        identity.HasClaim(c => c.Type == "preferred_username"))
                                        identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, identity.FindFirst("preferred_username").Value));
    
    
    
                                // Check if token names are stored in Properties
                                if (context.Properties.Items.ContainsKey(".TokenNames"))
                                {
                                    // Token names a semicolon separated
                                    string[] tokenNames = context.Properties.Items[".TokenNames"].Split(';');
    
                                    // Add each token value as Claim
                                    foreach (var tokenName in tokenNames)
                                    {
                                        // Tokens are stored in a Dictionary with the Key ".Token.<token name>"
                                        string tokenValue = context.Properties.Items[$".Token.{tokenName}"];
                                        identity.AddClaim(new Claim(tokenName, tokenValue));
                                    }
                                }
    
                            }
    
                            return Task.CompletedTask;
                        }
                    };
                });
            }
    

    Okta Widget

  • 0
    maharatha created

    Do you mind sharing the code changes you did in the angular side ?

  • 0
    cmthomps created

    My project is .NET Core/jQuery. Sorry...

  • 0
    maharatha created

    No problem, thank you for the response.

  • 0
    ismcagdas created

    Thanks a million again @cmthomps for sharing this :)