Cannot get groups claim in .NET Framework MVC App

For the life of me I cannot figure out why in .NET Core/5 apps I can get groups scope to work, but I cannot in a .NET Framework 4.6.1 application. I’ve used the exact same application info (client id, client secret, domain) from our Okta tenant.

Here is the code from a .NET 5 app Startup.cs file that works fine:

 public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(options =>
            {
                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            })
            .AddCookie()
            .AddOpenIdConnect(options =>
            {
                options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.Authority = Configuration["Okta:Domain"];
                options.RequireHttpsMetadata = true;
                options.ClientId = Configuration["Okta:ClientId"];
                options.ClientSecret = Configuration["Okta:ClientSecret"];
                options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
                options.GetClaimsFromUserInfoEndpoint = true;
                options.Scope.Add("openid");
                options.Scope.Add("profile");
                options.Scope.Add("groups");
                options.SaveTokens = true;
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = "name",
                    RoleClaimType = "groups",
                    ValidateIssuer = true
                };

                options.ClaimActions.Add(new JsonKeyClaimAction(ClaimTypes.Role, "string", "groups"));
            });
            services.AddControllersWithViews();
        }

Here is one version of code from a .NET Framework app Startup.cs file that does not work:

public void Configuration(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions());

            app.UseOktaMvc(new OktaMvcOptions
            {
                OktaDomain = authority,
                ClientId = clientId,
                ClientSecret = clientSecret,
                RedirectUri = redirectUri,
                PostLogoutRedirectUri = postLogoutRedirectUri,
                Scope = new List<string> {
                    "openid",
                    "profile",
                    "groups"
                },
                GetClaimsFromUserInfoEndpoint = true
            });
        }

It gives the following error:

OpenIdConnectMessage.Error was not null, indicating an error. Error: ‘invalid_scope’. Error_Description (may be empty): ‘One or more scopes are not configured for the authorization server resource.’. Error_Uri (may be empty): ‘error_uri is null’."

Here is another attempt I made using a different library, on the instructions of these pages

:

 public void Configuration(IAppBuilder app)
{
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions());

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
            {
                ClientId = clientId,
                RequireHttpsMetadata = true,
                ClientSecret = clientSecret,
                Authority = authority,
                RedirectUri = redirectUri,
                SaveTokens = true,
                ResponseType = OpenIdConnectResponseType.CodeIdToken,
                Scope = OpenIdConnectScope.OpenIdProfile + " email phone address groups",
                PostLogoutRedirectUri = postLogoutRedirectUri,
                TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = "name",
                    RoleClaimType = "groups",
                    ValidateIssuer = true
                },
                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthorizationCodeReceived = async n =>
                    {
                        // Exchange code for access and ID tokens
                        var tokenClient = new TokenClient(authority + "/v1/token", clientId, clientSecret);
                        var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, redirectUri);

                        if (tokenResponse.IsError)
                        {
                            throw new Exception(tokenResponse.Error);
                        }

                        var userInfoClient = new UserInfoClient(authority + "/v1/userinfo");
                        var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
                        var claims = new List<Claim>();
                        claims.AddRange(userInfoResponse.Claims);
                        claims.Add(new Claim("id_token", tokenResponse.IdentityToken));
                        claims.Add(new Claim("access_token", tokenResponse.AccessToken));

                        if (!string.IsNullOrEmpty(tokenResponse.RefreshToken))
                        {
                            claims.Add(new Claim("refresh_token", tokenResponse.RefreshToken));
                        }

                        n.AuthenticationTicket.Identity.AddClaims(claims);

                        return;
                    },

                    RedirectToIdentityProvider = n =>
                    {
                        // If signing out, add the id_token_hint
                        if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
                        {
                            var idTokenClaim = n.OwinContext.Authentication.User.FindFirst("id_token");

                            if (idTokenClaim != null)
                            {
                                n.ProtocolMessage.IdTokenHint = idTokenClaim.Value;
                            }

                        }

                        return Task.CompletedTask;
                    }
                },
            });
        }
    }

When using the same domain value as done in the .NET 5 application (https://{our Okta domain}), this gives a simple Not Found error. When using the domain given in the above linked tutorial (https://our Okta domain}/oauth2/default) it gives the same error as the other set of code above did:

OpenIdConnectMessage.Error was not null, indicating an error. Error: ‘invalid_scope’. Error_Description (may be empty): ‘One or more scopes are not configured for the authorization server resource.’. Error_Uri (may be empty): ‘error_uri is null’."

If the authorization server does not have the configuration for that scope, why am I able to get the groups scope to work using the same client id and client secret in the .NET Core/5 apps? Is there some magic that the .NET Core libraries have that isn’t possessed by the .NET Framework equivalents?

Hi @david.pimentel! Can you try our samples from here instead GitHub - okta/samples-aspnet: samples-aspnet (from - Add User Authentication and Okta Resource Management to Your ASP.NET App | Okta Developer) - perhaps the sample you are using is outdated.

Hi @sigama ,
Thank you for the response and the information about more updated GitHub examples. Unfortunately, I tried the one that best fit my scenario (okta-hosted-login) and ran into the same problem. It worked fine until I added the groups scope in the Startup.cs file, like so:

public void Configuration(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions());

            app.UseOktaMvc(new OktaMvcOptions()
            {
                OktaDomain = ConfigurationManager.AppSettings["okta:OktaDomain"],
                ClientId = ConfigurationManager.AppSettings["okta:ClientId"],
                ClientSecret = ConfigurationManager.AppSettings["okta:ClientSecret"],
                AuthorizationServerId = ConfigurationManager.AppSettings["okta:AuthorizationServerId"],
                RedirectUri = ConfigurationManager.AppSettings["okta:RedirectUri"],
                PostLogoutRedirectUri = ConfigurationManager.AppSettings["okta:PostLogoutRedirectUri"],
                GetClaimsFromUserInfoEndpoint = true,
                Scope = new List<string> {"openid", "profile", "email", "groups"},
            });
        }

The error I get is yet again

“OpenIdConnectMessage.Error was not null, indicating an error. Error: ‘invalid_scope’. Error_Description (may be empty): ‘One or more scopes are not configured for the authorization server resource.’. Error_Uri (may be empty): ‘error_uri is null’.”

Is there some alternate way I can get AD groups to map to Roles for the Authorize attribute in a .NET Framework app? That is my end goal.

If you’re using a custom authorization server, the ‘groups’ scope will not be created for you, like it is when using the built-in (un-customizable) Org authorization server. Details about the different authorization servers found here: Authorization Servers | Okta Developer

When using a custom auth server, you can instead simply create your own groups claim on the authorization server in question (by default, this tends to be the one we unhelpfully call “Default,” which you can find in the admin console under Security → API → Authorization Servers). Whether or not this is a scope-dependent claim is up to you, so you don’t necessarily even need to request a ‘groups’ scope (that you would also have to create) to get the group membership information in your token if you don’t wish to.

Full details about setting up this claim found here (this guide does not make the claim scope-dependent): Add a Groups claim for a Custom Authorization Server | Okta Developer

1 Like

I rewrote the whole application in .NET 5. That did the trick.

Also, I found this thread that has a solution that evidently works for me as I’m trying again to get Open Id authentication to work in .NET Framework.

It uses the Owin.OpenIdConnectAuthenticationExtensions.UseOpenIdConnectAuthentication method rather than Okta.AspNet.OktaMiddlewareExtensions.UseOktaMvc. Is there perhaps a bug in the latter method?

Hi David,

You are mixing Katana (.NET 4) and Core (.NET 5) Owin. Completely different implementations of the Owin framework. It is entirely possible that Katana is not supporting what you are looking for. From what you say here I suspect that this is an Owin issue, not an Okta issue.

I could help you take a closer look if you need, but I’m not clear where you stand right now from what I’m reading. Are you OK with .NET 5 or are you stuck on .NET 4 being the platform you have to use? I get it if you are, I just came off a project stuck on .NET 4.8 using Katana as the implementation.

Joel