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?