I've been trying to setup a project with IdentityServer4 for a while. However I'm getting the following error:
Sso.Application.CentralHandler: Information: AuthenticationScheme: central was challenged.
IdentityServer4.Hosting.IdentityServerMiddleware: Information: Invoking IdentityServer endpoint: IdentityServer4.Endpoints.AuthorizeEndpoint for /connect/authorize
IdentityServer4.Validation.AuthorizeRequestValidator: Error: Unknown client or not enabled: oauthClient
IdentityServer4.Endpoints.AuthorizeEndpoint: Error: Request validation failed
IdentityServer4.Endpoints.AuthorizeEndpoint: Information: {
"SubjectId": "anonymous",
"RequestedScopes": "",
"PromptMode": "",
"Raw": {
"client_id": "oauthClient",
"scope": "weatherforecasts.read",
"response_type": "code",
"redirect_uri": "https://localhost:44375/signin-central",
"code_challenge": "Rdi0rU5OkG1gWzh9xfvOxbZLiGbDHqujbMzl9d3u7Qs",
"code_challenge_method": "S256",
"state": "CfDJ8PC7ZLg_v2RDsl0VaXUuuT_-sT-at-LgQD1krwu8LESVXDKkQxQd8_eUQZJqOiGREAzBtfZ4U9X0BJDIn15AvYXKR2omUEBW5LzJm1Vz3ykaScc_kC89f6hCimDBmqCAdUOF0wnEn8FfDD8GPJtPBgxqoqrCNnyGKxh58XOIa85sN-zDSU5Oa73pzKt5FrFIkBCqUOfpCM_KZajZR_3DWFNCbwn8tS-XR0of7ga72XDILC--N9bCqA2eIlTSxf9HHPXmmLninU1ri7RM-XMsOzH__mtQQPOXCuaHw3Q0Nkedmpj4NaTCdcB1k55IdsX1eLrub8ptagCWzMIzXcYIWlJc74Zj-_H2uDZE4M-Blbdr"
}
}
I've been looking on SO for how to solve this error for the entire day, but I can't figure out what's wrong with it.
This is the code in the Startup of the IdentityProvider project:
services
.AddDbContext<SsoCentralContext>();
//.AddScoped<Repositories.IAccountRepository, Repositories.AccountRepository>();
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<SsoCentralContext>();
var isb = services.AddIdentityServer();
isb
.AddInMemoryClients(new List<Client>
{
new Client
{
ClientId = "oauthClient",
ClientName = "oauthClient",
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
Enabled = true,
ClientSecrets = new List<Secret> {new Secret("SuperSecretPassword".Sha256())}, // change me!
AllowedScopes = new List<string> {"weatherforecasts.read"},
RedirectUris = new List<string>
{
"https://localhost:44375/signin-central"
},
}
})
.AddInMemoryIdentityResources(new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResource
{
Name = "role",
UserClaims = new List<string> {"role"}
}
})
.AddInMemoryApiResources(new List<ApiResource>
{
new ApiResource
{
Name = "api1",
DisplayName = "API #1",
Description = "Allow the application to access API #1 on your behalf",
Scopes = new List<string> { "weatherforecasts.read", "weatherforecasts.write"},
ApiSecrets = new List<Secret> {new Secret("ScopeSecret".Sha256())},
UserClaims = new List<string> {"role"}
}
})
.AddInMemoryApiScopes(new List<ApiScope>
{
new ApiScope("weatherforecasts.read", "Read Access to API #1"),
new ApiScope("weatherforecasts.write", "Write Access to API #1")
})
.AddTestUsers(new List<IdentityServer4.Test.TestUser>
{
new IdentityServer4.Test.TestUser
{
SubjectId = "5BE86359-073C-434B-AD2D-A3932222DABE",
Username = "Pieterjan",
Password = "password",
Claims = new List<System.Security.Claims.Claim> {
new System.Security.Claims.Claim(IdentityModel.JwtClaimTypes.Email, "pieterjan#example.com"),
new System.Security.Claims.Claim(IdentityModel.JwtClaimTypes.Role, "admin")
}
}
})
.AddDeveloperSigningCredential();
isb
.AddOperationalStore(options =>
{
options.ConfigureDbContext = (builder) => builder.UseInMemoryDatabase("SsoCentral");
})
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = (builder) => builder.UseInMemoryDatabase("SsoCentral");
});
isb.AddAspNetIdentity<IdentityUser>();
The above code is definitely being called, so the oauthClient should exist for sure. Also the client is definitely enabled.
This is the code in Startup of the Identity project:
services
.AddAuthentication(options =>
{
})
.AddOAuth<CentralOptions, CentralHandler>("central", options =>
{
options.ClaimsIssuer = "https://localhost:44359"; // This is the URL of the IdentityProvider
options.SaveTokens = true;
options.ClientId = "oauthClient";
options.ClientSecret = "SuperSecretPassword";
options.Scope.Add("weatherforecasts.read");
options.UsePkce = true;
});
How can I fix this error? Would anyone know how to figure out what's wrong here?
Also would I still need to use OpenIdConnect on top of what's been configured here?
Update:
I've added a call just to get the clients from the IS4 ClientStore:
[HttpGet("Clients")]
public async Task<IActionResult> GetClients()
{
//var client = await clientStore.FindClientByIdAsync("SsoApplicationClient");
var _inner = (IdentityServer4.EntityFramework.Stores.ClientStore)clientStore.GetType().GetField("_inner", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(clientStore);
var Context = (IdentityServer4.EntityFramework.DbContexts.ConfigurationDbContext)_inner.GetType().GetField("Context", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(_inner);
var Clients = Context.Clients;
return Ok(Clients);
}
To my amazement, what I get from this is an entirely empty list:
Alright, so when you have the following configuration:
services.AddIdentityServer()
...
.AddOperationalStore(options =>
{
options.ConfigureDbContext = (builder) => builder.UseInMemoryDatabase("SsoCentral");
})
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = (builder) => builder.UseInMemoryDatabase("SsoCentral");
})
The InMemoryClients don't exist anymore. Just commented it out and it seems to be working now.
Related
I'm adding Identity Server to my existing project. Basically I have everything in place, but when I do a request to the API, the User.Identity.Name is null. However the User.Identity.Claims contains the name claim:
I'm aware of the way of getting the user name by HttpContext.User.FindFirstValue(ClaimTypes.Name), but it would require a lot of code refactoring, so I'd rather avoid this way.
I configured the ApiResources in Identity server the following way:
public static IEnumerable<ApiResource> ApiResources => new[]
{
new ApiResource
{
Name = "my-api",
DisplayName = "My API",
Description = "My API",
Scopes = new List<string> { "my-api"},
UserClaims = new List<string> {
JwtClaimTypes.Email,
JwtClaimTypes.Name,
JwtClaimTypes.Subject,
JwtClaimTypes.Role,
}
}
};
and the client:
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
// ...
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"name",
"roles",
"my-api",
}
}
};
Authentication setup in API project:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = config["IdentityServer:Domain"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = config["IdentityServer:Domain"],
ValidateAudience = false
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", "my-api");
});
});
Please advice what am I doing wrong?
The problem is that Microsoft and OpenID connect have different opinion on what the name of the "name" claim should be. So what you need to do is to tell the system what the name of the name claim is, by:
.AddJwtBearer(opt =>
{
...
opt.TokenValidationParameters.RoleClaimType = "roles";
opt.TokenValidationParameters.NameClaimType = "name";
...
}
I use identity server 4 with blazor server side client
everything is ok but token not authorized api methods but token works in server authorized controllers
is something wrong with grant type or code flow ?
server config class :
public static class Configurations
{
public static IEnumerable<IdentityResource> GetIdentityResources() =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
public static IEnumerable<ApiResource> GetApis() =>
new List<ApiResource> {
new ApiResource("api1")
};
public static IEnumerable<ApiScope> GetApiScopes()
{
return new List<ApiScope>
{
// backward compat
new ApiScope("api1")
};
}
public static IEnumerable<Client> GetClients() => new List<Client>
{
new Client
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.Code,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = {
"api1" ,
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
RedirectUris = { "https://localhost:44372/signin-oidc" },
AlwaysIncludeUserClaimsInIdToken = true,
AllowOfflineAccess = true,
RequireConsent = false,
RequirePkce = true,
}
};
}
server start up class :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(config =>
{
config.UseInMemoryDatabase("Memory");
});
// AddIdentity registers the services
services.AddIdentity<IdentityUser, IdentityRole>(config =>
{
config.Password.RequiredLength = 4;
config.Password.RequireDigit = false;
config.Password.RequireNonAlphanumeric = false;
config.Password.RequireUppercase = false;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(config =>
{
config.Cookie.Name = "IdentityServer.Cookie";
config.LoginPath = "/Auth/Login";
config.LogoutPath = "/Auth/Logout";
});
services.AddIdentityServer()
.AddAspNetIdentity<IdentityUser>()
//.AddInMemoryApiResources(Configurations.GetApis())
.AddInMemoryIdentityResources(Configurations.GetIdentityResources())
.AddInMemoryApiScopes(Configurations.GetApiScopes())
.AddInMemoryClients(Configurations.GetClients())
.AddDeveloperSigningCredential();
services.AddControllersWithViews();
}
api start up class :
services.AddAuthentication("Bearer").AddIdentityServerAuthentication(option =>
{
option.Authority = "https://localhost:44313";
option.RequireHttpsMetadata = false;
option.ApiName = "api1";
});
blazor server side start up class:
services.AddAuthentication(config =>
{
config.DefaultScheme = "Cookie";
config.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookie")
.AddOpenIdConnect("oidc", config =>
{
config.Authority = "https://localhost:44313/";
config.ClientId = "client";
config.ClientSecret = "secret";
config.SaveTokens = true;
config.ResponseType = "code";
config.SignedOutCallbackPath = "/";
config.Scope.Add("openid");
config.Scope.Add("api1");
config.Scope.Add("offline_access");
});
services.AddMvcCore(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser() // site-wide auth
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
To fix this issue, you have 2 options:
1- (Recommended) To add the scopes to API resource like this:
public static IEnumerable<ApiResource> GetApis() =>
new List<ApiResource> {
new ApiResource("api1")
{
Scopes = new []{ "api1" }
}
};
public static IEnumerable<ApiScope> GetApiScopes()
{
return new List<ApiScope>
{
// backward compat
new ApiScope("api1")
};
}
2- On API change your code to set ValidateAudience = false, like this:
services.AddAuthentication("Bearer").AddJwtBearer("Bearer",
options =>
{
options.Authority = "http://localhost:5000";
options.Audience = "api1";
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new
TokenValidationParameters()
{
ValidateAudience = false
};
});
Here is my blog about migrating IdentityServer4 to v4 https://nahidfa.com/posts/migrating-identityserver4-to-v4/
I have not actually used AddIdentityServerAuthentication in API but can you try the below code. Technically its same thing, but maybe this will work.
Change your api authentication from AddIdentityServerAuthentication to AddJwtBearer:
services.AddAuthentication("Bearer").AddIdentityServerAuthentication(option =>
{
option.Authority = "https://localhost:44313";
option.RequireHttpsMetadata = false;
option.ApiName = "api1";
});
to
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", option =>
{
option.Authority = "https://localhost:44313";
option.Audience = "api1";
option.SaveToken = true;
});
adopting from scottbrady91.com, I'm trying to have an Apple external authentication on our site. I've had Microsoft one working, but not the Apple one yet. The user is already directed to appleid.apple.com, but after authentication, it's returned to https://iluvrun.com/signin-apple (which is correct), but this isn't handled and so the user gets a 404 error.
To be honest I don't know how signin-facebook, signin-google or signin-oidc work, but they just do. So I have problems figuring out why signin-apple isn't being handled.
The site is built using ASP.NET Web Forms. Below is what I have at Startup.Auth.cs:
namespace ILR
{
public partial class Startup {
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions("Apple")
{
ClientId = "com.iluvrun.login",
Authority = "https://appleid.apple.com/auth/authorize",
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
RedirectUri = "https://iluvrun.com/signin-apple",
PostLogoutRedirectUri = "https://iluvrun.com",
Scope = "name email",
ResponseType = OpenIdConnectResponseType.Code,
ResponseMode = OpenIdConnectResponseMode.FormPost,
CallbackPath = PathString.FromUriComponent("/signin-apple"),
Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = "https://appleid.apple.com/auth/authorize",
TokenEndpoint = "https://appleid.apple.com/auth/token"
},
TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "https://appleid.apple.com",
IssuerSigningKey = new JsonWebKeySet(GetKeysAsync().Result).Keys[0]
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = (context) =>
{
context.TokenEndpointRequest.ClientSecret = TokenGenerator.CreateNewToken();
return Task.CompletedTask;
},
AuthenticationFailed = (context) =>
{
context.HandleResponse();
context.Response.Redirect("/Account/Login?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
},
ProtocolValidator = new OpenIdConnectProtocolValidator
{
RequireNonce = false,
RequireStateValidation = false
}
}
);
}
private static async Task<string> GetKeysAsync()
{
string jwks = await new HttpClient().GetStringAsync("https://appleid.apple.com/auth/keys");
return jwks;
}
}
public static class TokenGenerator
{
public static string CreateNewToken()
{
const string iss = "CHM57Z5A6";
const string aud = "https://appleid.apple.com";
const string sub = "com.iluvrun.login";
const string privateKey = "XXXX"; // contents of .p8 file
CngKey cngKey = CngKey.Import(Convert.FromBase64String(privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob);
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
JwtSecurityToken token = handler.CreateJwtSecurityToken(
issuer: iss,
audience: aud,
subject: new ClaimsIdentity(new List<Claim> { new Claim("sub", sub) }),
expires: DateTime.UtcNow.AddMinutes(5),
issuedAt: DateTime.UtcNow,
notBefore: DateTime.UtcNow,
signingCredentials: new SigningCredentials(new ECDsaSecurityKey(new ECDsaCng(cngKey)), SecurityAlgorithms.EcdsaSha256));
return handler.WriteToken(token);
}
}
}
Does anyone have any clue what I miss to get this working?
You are on the right track and your question helped me to quick start my own solution for Apple ID OpenIdConnect OWIN integration in my project. After finding your post here it took me quite long to fix all issues.
After using your code I've got the same 404 error.
404 error
This was due to unhandled exception in CreateNewToken() method, which wasn't able to generate valid token. In my case it was missing Azure configuration for my AppService more described in CngKey.Import on azure:
WEBSITE_LOAD_USER_PROFILE = 1
After setting this configuration in Azure I moved to next issue:
Token endpoint wasn't called
This was due to missing configuration in OpenIdConnectAuthenticationOptions:
RedeemCode = true
This option trigger all the next processing of the authentication pipeline inside OpenIdConnect (TokenResponseReceived, SecurityTokenReceived, SecurityTokenValidated)
AccountController.ExternalLoginCallback token processing issue
This was tricky. Because all you get is after calling
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
is getting:
loginInfo == null
So after reading lot of issue articles on this topic like OWIN OpenID provider - GetExternalLoginInfo() returns null and tries, the only final workaround was to add https://github.com/Sustainsys/owin-cookie-saver to my Startup.cs, which fixed problem of missing token cookies. Its marked as legacy, but it was my only option to fix this.
So final OpenIdConnect options config for my working solution is:
var appleIdOptions = new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "https://appleid.apple.com",
ClientId = "[APPLE_CLIENT_ID_HERE]",
Authority = "https://appleid.apple.com/auth/authorize",
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
RedirectUri = "https://www.europeanart.eu/signin-apple",
PostLogoutRedirectUri = "https://www.europeanart.eu",
Scope = OpenIdConnectScope.Email,
RedeemCode = true,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
ResponseMode = OpenIdConnectResponseMode.FormPost,
CallbackPath = PathString.FromUriComponent("/signin-apple"),
Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = "https://appleid.apple.com/auth/authorize",
TokenEndpoint = "https://appleid.apple.com/auth/token"
},
TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "https://appleid.apple.com",
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = new JsonWebKeySet(GetKeys()).Keys,
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = (context) =>
{
var clientToken = JwtTokenGenerator.CreateNewToken();
logger.LogInfo("Apple: clientToken generated");
context.TokenEndpointRequest.ClientSecret = clientToken;
logger.LogInfo("Apple: TokenEndpointRequest ready");
return Task.FromResult(0);
},
TokenResponseReceived = (context) =>
{
logger.LogInfo("Apple: TokenResponseReceived");
return Task.FromResult(0);
},
SecurityTokenReceived = (context) =>
{
logger.LogInfo("Apple: SecurityTokenReceived");
return Task.FromResult(0);
},
SecurityTokenValidated = (context) =>
{
string userID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
logger.LogInfo("Apple: SecurityTokenValidated with userID=" + userID);
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
logger.LogInfo("Apple: RedirectToIdentityProvider");
if(context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
{
logger.LogInfo("Apple: RedirectToIdentityProvider -> Authenticate()");
}
else if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Token)
{
logger.LogInfo("Apple: RedirectToIdentityProvider -> Token()");
}
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
context.HandleResponse();
logger.LogError("Apple Authentication Failed.", context.Exception);
context.Response.Redirect("/Account/Login?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
},
ProtocolValidator = new OpenIdConnectProtocolValidator
{
RequireNonce = false,
RequireStateValidation = false
}
};
I'm trying to understand how this works, so please bear with me.
Here is my config for identity server:
public static IEnumerable<ApiResource> GetApiResources(IConfiguration configuration)
{
return new []
{
new ApiResource
{
Name = "invoices.api",
ApiSecrets =
{
new Secret("invoices.api.secret".Sha256()),
},
Scopes =
{
new Scope("invoices.api.scope"),
},
UserClaims =
{
"custom_role",
}
}
};
}
public static IEnumerable<Client> GetClients(IConfiguration configuration)
{
return new []
{
new Client
{
ClientId = "invoices.ui",
RequireConsent = false,
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
AccessTokenType = AccessTokenType.Reference,
AllowedCorsOrigins = configuration.GetSection("Redirect").Get<RedirectOptions>().AllowedCorsOrigins.ToList(),
RedirectUris = configuration.GetSection("Redirect").Get<RedirectOptions>().RedirectUris.ToList(),
PostLogoutRedirectUris = configuration.GetSection("Redirect").Get<RedirectOptions>().PostLogoutRedirectUris.ToList(),
ClientSecrets =
{
new Secret("invoices.ui.secret".Sha256())
},
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
"invoices.api.scope",
},
}
};
}
public static IEnumerable<TestUser> GetUsers(IConfiguration configuration)
{
return new []
{
new TestUser
{
SubjectId = "1",
Username = "alice",
Password = "123",
Claims =
{
new Claim("custom_role", "user"),
},
},
new TestUser
{
SubjectId = "2",
Username = "bob",
Password = "123",
Claims =
{
new Claim("custom_role", "admin"),
},
}
};
}
public static IEnumerable<IdentityResource> GetIdentityResources(IConfiguration configuration)
{
return new []
{
new IdentityResources.OpenId(),
};
}
And this is how my MVC client is setup:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(opts =>
{
//opts.ExpireTimeSpan = TimeSpan.FromSeconds(60);
})
.AddOpenIdConnect("oidc", opts =>
{
opts.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opts.DisableTelemetry = true;
opts.Authority = Configuration.GetValue<string>("IdentityServer");
opts.RequireHttpsMetadata = false;
opts.ClientId = "invoices.ui";
opts.ClientSecret = "invoices.ui.secret";
opts.ResponseType = "code id_token";
opts.SaveTokens = true;
opts.GetClaimsFromUserInfoEndpoint = true;
opts.Scope.Clear();
opts.Scope.Add("openid");
opts.Scope.Add("invoices.api.scope");
});
After a user is authenticated, i'm trying to see it's claims in view like this:
#foreach (var claim in User.Claims)
{
<dt>#claim.Type</dt>
<dd>#claim.Value</dd>
}
But the list doesn't contain any "custom_role" claim.
The identity server logs shows that the user info has been requested by the client from user info endpoint, but my "custom_role" wasn't transfered there, however it shows in logs of identity server, that user has it.
How to access my custom claims in my MVC app?
I need to get them from user endpoint and use for authorization.
If you ask for an Access Token and Identity Token ("code id_token") Identity Server will not include user claims by default.
The solution is to set AlwaysIncludeUserClaimsInIdToken to true. See http://docs.identityserver.io/en/release/reference/client.html
The explanation on why this settings exists is here: https://leastprivilege.com/2016/12/14/optimizing-identity-tokens-for-size/
Seems that adding an identity resource with specified claims solves the problem even with built-in ProfileService implementation:
public static IEnumerable<IdentityResource> GetIdentityResources(IConfiguration configuration)
{
return new []
{
new IdentityResources.OpenId(),
new IdentityResource
{
Name = "roles.scope",
UserClaims =
{
"custom_role",
}
}
};
}
Also added it as a scope for a client:
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
"invoices.api.scope",
"roles.scope",
},
I want to use the function IntrospectionClient to verify the token in identity server, but it always returns: Unauthorized
Identity server startup code:
services.AddIdentityServer()
.AddSigningCredential(myPrivatecert)
.AddInMemoryApiResources(Config.GetResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetTestUsers())
.AddValidationKey(myPubliccert)
.AddJwtBearerClientAuthentication()
.AddValidators();
I add a api resource, a client info and a test user in config file
My Config is :
public class Config
{
//ApiResource
public static IEnumerable<ApiResource> GetResources()
{
return new List<ApiResource>
{
new ApiResource("api")
};
}
//Client
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client()
{
ClientId = "site",
AccessTokenLifetime = 180,
RefreshTokenExpiration = TokenExpiration.Absolute,
AbsoluteRefreshTokenLifetime = 1800,
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AlwaysSendClientClaims = true,
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
AllowedScopes =
{
"api"
},
AccessTokenType = AccessTokenType.Jwt,
AllowOfflineAccess = true
},
};
}
//TestUser
public static List<TestUser> GetTestUsers()
{
return new List<TestUser>{
new TestUser{
SubjectId="1",
Username="wyt",
Password="123456",
Claims = new List<Claim>()
{
new Claim("wawa", "aoao"),
}
}
};
}
}
My Client Startup is:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).
AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:6001";
options.RequireHttpsMetadata = false;
options.ApiName = "api";
});
get token :
//token
var tokenClient = new TokenClient(dico.TokenEndpoint, "site", "secret");
var tokenResponse = tokenClient.RequestResourceOwnerPasswordAsync("wyt", "123456").Result;
want use introspection:
var introspectionClient = new IntrospectionClient(
dico.IntrospectionEndpoint,
"api",
"secret");
var vresponse = await introspectionClient.SendAsync
(
new IntrospectionRequest { Token = tokenResult.AccessToken }
);
but the vresponse is always Unauthorized
where is wrong?
I ran into the same issue, the documentation is not very clear.
You need to add an ApiSecret to your ApiResource:
public static IEnumerable<ApiResource> GetResources()
{
return new List<ApiResource>
{
new ApiResource("api")
{
ApiSecrets = { new Secret("secret".Sha256()) }
}
};
}
And then you call the introspection endpoint passing the api name and the api secret, like you did:
var introspectionClient = new IntrospectionClient(
dico.IntrospectionEndpoint,
"api",
"secret");