So I was following the quick starts from the latest IdentityServer/Duende official resources.
From what I understood, An Api Resource is essentially logical grouping of apiscopes and identityscopes. You can Have 3 ApiScopes "apiscope1", "apiscope2","apiscope3", wrapped in an ApiResource "api".
When setting the client, you need to specify "api" in scopes, which will automatically give you apiscope1, apiscope2, apiscope3.
1. Is this correct?
I have two Apis: MyApi and IdentityServerApi.
MyApi is configured as follows:
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
var idsvrConfig = builder.Configuration.GetSection("IdentityServer").Get<IdentityServerConfiguration>();
options.Authority = idsvrConfig.Authority;
options.ClientId = idsvrConfig.ClientId;
options.ClientSecret = idsvrConfig.ClientSecret;
options.ResponseType = idsvrConfig.ResponseType;
options.Scope.Clear();
//options.Scope.AddRange(idsvrConfig.Scopes);
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("verification");
options.Scope.Add("apiResource"); //<-------Doesn't work when this is specified!!
options.GetClaimsFromUserInfoEndpoint = idsvrConfig.GetClaimsFromUserInfoEndpoint;
options.SaveTokens = idsvrConfig.SaveTokens;
foreach (var claims in idsvrConfig.ClaimActionsMapJsonKey)
{
options.ClaimActions.MapJsonKey(claims.Key, claims.Value);
}
});
Now for the Identity Server API Configuration:
var apiResources = new List<ApiResource>()
{
new ApiResource()
{
Name = "apiResource",
DisplayName ="ApiResource",
Scopes = new string[] {"test" }
}
};
var apiScopes = new List<ApiScope>()
{
new ApiScope()
{
Name = "test",
}
};
var identityResources = new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource()
{
Name = "verification",
UserClaims = new List<string>
{
JwtClaimTypes.Email,
JwtClaimTypes.EmailVerified
}
}
};
var clients = new List<Client>
{
// interactive ASP.NET Core Web App
new Client
{
ClientId = "api",
ClientSecrets = { new Secret(){
Value = "supersecretpass"
}},
AllowedGrantTypes = GrantTypes.Code,
// where to redirect after login
RedirectUris = { "https://localhost:44330/signin-oidc" },
// where to redirect after logout
PostLogoutRedirectUris = { "https://localhost:44330/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"verification",
"apiResource",
"test"
}
}
};
// Add Identity Server services
builder.Services.AddIdentityServer()
.AddInMemoryClients(clients)
.AddInMemoryIdentityResources(identityResources)
.AddInMemoryApiScopes(apiScopes)
.AddInMemoryApiResources(apiResources)
//.AddInMemoryClients(builder.Configuration.GetSection("IdentityServer:Clients"))
//.AddInMemoryIdentityResources(builder.Configuration.GetSection("IdentityServer:IdentityResources"))
//.AddInMemoryApiScopes(builder.Configuration.GetSection("IdentityServer:ApiScopes"))
//.AddInMemoryApiResources(builder.Configuration.GetSection("IdentityServer:ApiResources"))
.AddTestUsers(TestUsers.Users);
2. Why is it that when on MyApi I add the scope "apiResource", I get an invalid scope screen error but when I remove it from the scopes requested by the client the full login/logout flow works?
Any help/insight is appreciated.
Just in case the versions make any difference, I'm running the following packages:
Both Api are running .NET 7
For MyApi: Microsoft.AspNetCore.Authentication.OpenIdConnect 7.0.5
For Idsvr: Duende.IdentityServer 6.2.3
You set the Scope
containing the apiResource
field in the client, but did not configure it in the server, Invalid scope
is caused because the Scope
cannot match.
You need to specify apiResource
in AllowedScopes
of Client
in IdentityServerApi
:
new Client
{
ClientId = "api",
ClientSecrets = { new Secret(){
Value = "supersecretpass"
}},
AllowedGrantTypes = GrantTypes.Code,
// where to redirect after login
RedirectUris = { "https://localhost:44330/signin-oidc" },
// where to redirect after logout
PostLogoutRedirectUris = { "https://localhost:44330/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"verification",
"apiResource"
}
}
Then you add the scope apiResource
in MyApi
, you should be able to successfully run.
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "<idsvr-url>";
options.ClientId = "api";
options.ClientSecret = "supersecretpass";
options.ResponseType = "code";
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("verification");
options.Scope.Add("apiResource");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
});
For more details about ApiResource
and ApiScope
, you can refer to this link.
Update:
The scopes added in AllowedScopes
should be defined. You only defined test
in apiScopes
, not apiResource
.
var apiScopes = new List<ApiScope>()
{
new ApiScope()
{
Name = "test",
},
//add this
new ApiScope
{
Name = "apiResource"
},
};
Regarding the "Aud" claim, you don't need to manually configure it. When you add an ApiResource
and specify the corresponding Scope
, when the client uses this Scope
to make a request, the obtained Token
will have the Aud
claim by default.
For example, I added two ApiResources
and specified the corresponding Scope
:
new List<ApiResource>
{
new ApiResource()
{
Name = "apiResource",
DisplayName ="ApiResource",
Scopes = new string[] {"test" }
},
new ApiResource()
{
Name = "paymentApi",
DisplayName = "PaymentApi",
Scopes= new string[] {"test2"}
}
};
Then add the corresponding Scope
in ApiScope
:
new List<ApiScope>
{
new ApiScope()
{
Name = "test"
},
new ApiScope
{
Name = "apiResource"
},
new ApiScope
{
Name = "test2"
}
};
Configure Client
in IdentityServerApi
(I'm not sure whether your ClientSecrets
can be verified, because I configured it according to the official document):
new Client
{
ClientId = "web",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
// where to redirect to after login
RedirectUris = { "https://localhost:5002/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"apiResource",
"test",
"test2"
}
}
In ClientApi:
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "web";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("apiResource");
options.Scope.Add("test");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
});
I can see .Token.access_token
after successfully logging in with the example built by referring to the official document:
Copy this access_token
and paste in jwt.io, you can see that your ApiResource
is included in the aud
:
When I add a line in ClientApi:
options.Scope.Add("test2");
You can see both apiResource
and paymentApi
are included in aud
: