I have an Identity server 4 application which i am trying to login to using my Xamarin.forms app using PKCE. I keep getting an error from the identity server one that i have actually not seen before. code_challenge is missing I am guessing that i am using the wrong grant type but all the documentation i have found for Xamarin says i should be using this one.
How do you connect Xamarin to Identity server 4?
The error
fail: IdentityServer4.Validation.AuthorizeRequestValidator[0]
code_challenge is missing
{
"ClientId": "xamarin",
"ClientName": "eShop Xamarin OpenId Client",
"RedirectUri": "1046123799103-h63f9o1cnj78fo26okng1aacr9e89u2e:/oauth2redirect",
"AllowedRedirectUris": [
"http://localhost:5001/signin-oidc"
],
"SubjectId": "anonymous",
"ResponseType": "code",
"ResponseMode": "query",
"GrantType": "authorization_code",
"RequestedScopes": "",
"State": "egfczresvcjyeerw",
"Raw": {
"client_id": "xamarin",
"redirect_uri": "1046123799103-h63f9o1cnj78fo26okng1aacr9e89u2e:/oauth2redirect",
"scope": "profile openid nol_api navinfo",
"response_type": "code",
"state": "egfczresvcjyeerw"
}
}
Client id in Identity server
new Client
{
ClientId = "xamarin",
ClientName = "eShop Xamarin OpenId Client",
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = { "http://localhost:5001/signin-oidc" },
RequireConsent = false,
RequirePkce = true,
PostLogoutRedirectUris = { "http://localhost:8008/Account/Redirecting" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"navinfo",
$"{nolConfig.Client}_api"
},
AllowOfflineAccess = true,
AllowAccessTokensViaBrowser = true,
RequireClientSecret = false
}
xamarin code
Authenticator = new OAuth2Authenticator
(
_clientId,
_secret,
_scopes,
new Uri(_discoveryDoc.AuthorizationEndpoint),
_redirectUri,
new Uri(_discoveryDoc.TokenEndpoint),
null,
isUsingNativeUI: true
);
If I remove RequirePkce = true, from the client in the identity server I no longer get the error in question. From what I have been able to find it would seam that Xamarin.auth doesn't support PKCE yet. Which means i will either have to disable it or implement it myself.
How do Login to Identity server 4 from XAmarin forms with PKCE enabled.
It looks like PKCE is enabled in OAuth2Authenticator by not having a client secret:
protected bool IsProofKeyCodeForExchange
{
get
{
return
accessTokenUrl != null // AccessToken url is defined
&&
string.IsNullOrWhiteSpace(clientSecret) // Client Secret is not defined
;
}
}
So I would try
Authenticator = new OAuth2Authenticator
(
_clientId,
null,
_scopes,
new Uri(_discoveryDoc.AuthorizationEndpoint),
_redirectUri,
new Uri(_discoveryDoc.TokenEndpoint),
null,
isUsingNativeUI: true
);
Related
I have an old IdentityServer 3 implementation and I'm trying not to change it to a newer version, I have been trying to authenticate a Blazor wasm application with it and so far almost everything went fine
Blazor Program.cs
...
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("Local", options.ProviderOptions);
});
Blazor appsettings.json
{
"Local": {
"Authority": "https://localhost:44343/",
"ClientId": "clientid",
"RedirectUri": "https://localhost:7170/authentication/login-callback",
"PostLogoutRedirectUri": "https://localhost:7170/authentication/logout-callback",
"ResponseType": "code"
}
}
just for testing purposes I added a new in memory client in my identityserver
IdentityServer 3 Clients
new Client {
ClientName = "client",
ClientId = "clientid",
Enabled = true,
AccessTokenType = AccessTokenType.Reference,
Flow = Flows.AuthorizationCodeWithProofKey,
RequireConsent = false,
ClientSecrets = new List < Secret > {
new Secret("secret".Sha256())
},
// where to redirect to after login
RedirectUris = {
"https://localhost:7170/authentication/login-callback",
"https://localhost:7170/connect/authorize/callback"
},
// where to redirect to after logout
PostLogoutRedirectUris = {
"https://localhost:7170/authentication/logout-callback"
},
AllowAccessToAllScopes = true,
AllowedScopes = new List < string > {
Constants.StandardScopes.OpenId,
Constants.StandardScopes.Profile,
Constants.StandardScopes.Email,
Constants.StandardScopes.Roles,
"Api"
}
}
I tried both with
Flow = Flows.AuthorizationCodeWithProofKey
and
Flow = Flows.AuthorizationCode
the problem is that when I try and login everything works fine until a /connect/token request is issued to which the server responds with invalid_client
I might have a clue on why this is happening but so far nothing I tried has done anything
as far as I know IdentityServer 3 requires the client secret and I didn't find a way around this so I tried to set it on the appsettings with:
"ClientSecret" : "secret"
but nothing changes
Ok, I am failing to get IS4 to issue a refresh token during authorization_code flow, even though I (believe) turned it on, and the client is requesting it by including offline_access in the scope. I add the IS4 using:
services.AddIdentityServer()
.AddInMemoryClients(Config.Clients)
.AddPersistedGrantStore<PersistedGrantStore>()
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddSigningCredential(loadedX509pfx);
// identity resources from another file:
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
Client is defined as (I included everything except URLs for brevity):
ClientId = "MyClientBlahBlah",
ClientSecrets = new List<Secret> { new("secretXX".Sha256()) },
RequireClientSecret = false,
RequireConsent = false,
AllowedGrantTypes = GrantTypes.Code,
AccessTokenType = AccessTokenType.Jwt,
AccessTokenLifetime = (int)TimeSpan.FromDays(7).TotalSeconds,
IdentityTokenLifetime = (int)TimeSpan.FromDays(7).TotalSeconds,
AbsoluteRefreshTokenLifetime = (int)TimeSpan.FromDays(30).TotalSeconds,
SlidingRefreshTokenLifetime = (int)TimeSpan.FromDays(15).TotalSeconds,
UpdateAccessTokenClaimsOnRefresh = true,
AllowOfflineAccess = true,
RefreshTokenExpiration = TokenExpiration.Sliding,
RefreshTokenUsage = TokenUsage.OneTimeOnly,
AlwaysSendClientClaims = true,
Enabled = true,
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess
},
RequirePkce = false,
...
However, when the request comes via this request:
GET /connect/authorize?client_id=MyClientBlahBlah&response_type=code&scope=openid%20profile%20offline_access&redirect_uri=<targeturl>
and the user logs in, the redirect URL is called only with authorization code:
There is no mention of refresh token anywhere. When I check the grant persistence store, the record was created (though its set to expire in 5 mins), however, again, the Data JSON in the record of store does not contain any mention of the refresh and there is no additional record being created for the subject.
{
"CreationTime" : "2022-03-28T15:56:26Z",
"Lifetime" : 300,
"ClientId" : "MyClientBlahBlah",
"Subject" : {...} <full json of claims, which is ok>
"IsOpenId" : true,
"RequestedScopes" : [
"0" : "opened",
"1" : "profile",
"2" : "offline_access"
],
"RedirectUri" : "targetURL",
"Nonce" : null,
"StateHash" : "d8EeV5-CyXT_-vbd8gRcRA",
"WasConsentShown" :false,
"SessionId" : "8DBD74DF791AD6C04FCCB78A3CF57C6E",
"CodeChallenge" : "",
"CodeChallengeMethod" : null,
"Description" : null,
"Properties" : { }
}
What am I doing wrong or what have I forgotten to include? I can't seem to find any help with documentation, most people have this problem when they forget to include offline_access in scope, which is not the case here.
The tokens (ID/Access/Refresh) are not meant be included in this request:
GET /connect/authorize?client_id=MyClientBlahBlah&response_type=code&scope=openid%20profile%20offline_access&redirect_uri=<targeturl>
Instead the OpenIDConnect handler in ASP.NET Core will after receiving the authorization code, ask for the tokens in a separate request between ASP.NET Core and IdentityServer. A request not visible to the user/browser.
Using the code below you can check what tokens that you actually have received:
string accessToken = await HttpContext.GetTokenAsync("access_token");
string idToken = await HttpContext.GetTokenAsync("id_token");
string refreshToken = await HttpContext.GetTokenAsync("refresh_token");
string tokenType = await HttpContext.GetTokenAsync("token_type");
string accessTokenExpire = await HttpContext.GetTokenAsync("expires_at");
I have a application which is like Microsoft default templates of Asp.netCoreWebApplication->ASP.netCoreWithReact.js.
In this react client will be wrapped inside a.netcore project. All of the UI pages will be served from React. .Net core backend will be used only for APIs.
Now I have implemented IdentityServer4 and able to generate token at this end point,
http://localhost:60739/token
From react client, on login button click I could make API call to http://localhost:60739/token and could generate token using granttype password flow. but i could not validate authroize my api end points with that token
below is my client definition with in identity server solution,
new Client
{
ClientName = "Resource Owner Flow",
ClientId = "resource_owner_flow",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("resource_owner_flow_secret".Sha256())
},
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.OfflineAccess
},
AllowOfflineAccess = true,
RefreshTokenUsage = TokenUsage.ReUse,
//AccessTokenLifetime = 60,
RefreshTokenExpiration = TokenExpiration.Absolute,
AbsoluteRefreshTokenLifetime = 300
}
below is piece of code i am using to refer identity server,
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "http://localhost:60739";
options.TokenValidationParameters.ValidateAudience = false;
options.TokenValidationParameters.ValidateIssuer = false;
options.TokenValidationParameters.ValidateIssuerSigningKey = false;
options.TokenValidationParameters.ValidateLifetime = false;
options.TokenValidationParameters.ValidateTokenReplay = false;
options.IncludeErrorDetails = true;
});
I could not successfully access protected api resources? Any idea ?
If i Call http://localhost:57102/api/Home/GetSomeProtectedData using token in header i get 500 internal server error. If i remove authorize attribute on protected resource it's working fine.
below is postman screenshot,
for token creation below is the log,
IdentityServer4.Validation.TokenRequestValidator: Information: Token request validation success,
{
"ClientId": "resource_owner",
"ClientName": "Resource Owner",
"GrantType": "password",
"Scopes": "openid",
"AuthorizationCode": "********",
"RefreshToken": "********",
"UserName": "superadmin#gmail.com",
"Raw": {
"username": "superadmin#gmail.com",
"password": "***REDACTED***",
"grant_type": "password",
"scope": "openid",
"response_type": "token"
}
}
but when I request protected API resource with token in header i get 500 internal server error and logs got created.
If you are getting 500 error, the problem is not the authorization flow. Else it would be 401.
You need to check out what is the real error. It can be a parsing error. You simpyly cannot finish the process because of an server side error. Please share more info, may be logs so I update the answer.
I have a client on IdentityServer ,which allows openid,profile and email scopes :
return new[] {
new Client
{
ClientId = "TestWebApp",
ClientSecrets = new [] { new Secret("TestSecret".Sha256()) },
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
AllowedScopes = new List<string>{ StandardScopes.OpenId, StandardScopes.Profile,StandardScopes.Email },
}
};
I have defined following Identity resources as well,
public static IEnumerable<IdentityResource> IdentityResources()
{
return new IdentityResource[] {
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email()
};
}
In-case the claim is missing , I am adding email to user claims explicitly while creation:
await _userManager.AddClaimAsync(testUser, new Claim("email", user.Username));
Now from my login controller using ResourceOwnerPasswordAndClientCredentials I am sending authentication request :
var client = new OAuth2Client(new Uri("http://localhost:44322/connect/token"), "TestWebApp", "TestSecret");
var requestResponse = client.RequestAccessTokenUserName(model.Email, model.Password, "openid profile email");
This works fine and I am getting the scopes back, but all of them are blank.
If you want to include the user claims in the Id token you can set AlwaysIncludeUserClaimsInIdToken to true on your client config.
return new[] {
new Client
{
ClientId = "TestWebApp",
ClientSecrets = new [] { new Secret("TestSecret".Sha256()) },
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
AllowedScopes = new List<string>{ StandardScopes.OpenId,
StandardScopes.Profile,StandardScopes.Email },
AlwaysIncludeUserClaimsInIdToken = true
}
};
You can include user claims in accesstoken when you specify those claims on Scopes. For instance for Swagger we needed to include the name claim if availible, below I dumped out the contents of what the ApiResource class should contain.
{
"ApiSecrets": [],
"Scopes": [
{
"Name": "SwaggerApi",
"DisplayName": "SwaggerApi",
"Description": null,
"Required": true,
"Emphasize": false,
"ShowInDiscoveryDocument": true,
"UserClaims": ["name","email"]
}
],
"Enabled": true,
"Name": "SwaggerApi",
"DisplayName": "SwaggerApi",
"Description": null,
"UserClaims": ["name","email"]
}
Add this scope to the allowed scopes of your client registration.
Request an access token.
If the User has a name claim or email claim -> it should get added to the access token.
Result contents access token
"idp": "oidc",
"name": "MyUserName",
"scope": [
"openid",
"profile",
"SwaggerApi"
],
When you use the resource owner password flow you’re requesting an access token, not an id token. Because of this, the claims associated with the scopes defined as identity resources are not passed in to your registered profile service implementation when the access token is created. If you really want to include the email in the access token then I’d advise you to make an api resource scope with “email” defined as a claim type.
That being said, if the email is being used for authentication purposes I’d suggest using another login flow that allows identity tokens if possible or using the user info endpoint.
I'm using Identity Server 4 in my Angular 5 application.
I configured Identity Server in this way:
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AllowAccessTokensViaBrowser = true,
RequireClientSecret = false,
AllowedScopes = {
"api1"
},
AllowedCorsOrigins = new List<string>
{
"http://localhost:4200"
}
}
};
}
public void ConfigureServices(IServiceCollection services)
{
var cors = new DefaultCorsPolicyService(_loggerFactory.CreateLogger<DefaultCorsPolicyService>())
{
AllowedOrigins = { "http://localhost:4200" }
};
services.AddSingleton<ICorsPolicyService>(cors);
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients());
}
And this is my Angular configuration:
export const oAuthDevelopmentConfig: AuthConfig = {
clientId: "client",
scope: "api1",
oidc: false,
issuer: "http://localhost:5000",
requireHttps: false
}
and I use configuration this way:
...
signin(): void {
this.oAuthService
.fetchTokenUsingPasswordFlowAndLoadUserProfile(this.model.username, this.model.password)
.then(() => {
this.authenticationService.init();
...
When I try to access to the server I receive the following error but I cannot understand where the problem is:
info: IdentityServer4.Hosting.IdentityServerMiddleware[0]
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.TokenEndpoint for /connect/token
info: IdentityServer4.Validation.NotSupportedResourceOwnerPasswordValidator[0]
Resource owner password credential type not supported. Configure an IResourceOwnerPasswordValidator.
fail: IdentityServer4.Validation.TokenRequestValidator[0]
Resource owner password credential grant type not supported
fail: IdentityServer4.Validation.TokenRequestValidator[0]
{
"ClientId": "client",
"GrantType": "password",
"Scopes": "api1",
"UserName": "admin#gmail.com",
"Raw": {
"grant_type": "password",
"client_id": "client",
"scope": "api1",
"username": "admin#gmail.com",
"password": "***REDACTED***"
}
}
What I miss?
This is an old question, but I did some head banging on the same problem today.
I discoverd this method: AddResourceOwnerValidator. This method may not have existed when this question was asked.
Here is my AddIdentityServer configuration.
services.AddIdentityServer(opt => opt.IssuerUri = issuerUri)
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(IdServer.Configuration.GetApiResources())
.AddInMemoryClients(IdServer.Configuration.GetClients())
.AddProfileService<ProfileService>()
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
;
At the time of the original question, the answer was probably like this, as shown in the question IdentityServer4 register UserService and get users from database in asp.net core
//Inject the classes we just created
services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
services.AddTransient<IProfileService, ProfileService>();