Identity Server login asks for code challenge - identityserver4

I have 3 projects
IdentityServer
MVC App
Web Api
That is works fine
MVC App: ConfigureServices
services.AddAuthentication(opt =>
{
opt.DefaultScheme = "Cookies";
opt.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", opt =>
{
opt.SignInScheme = "Cookies";
opt.Authority = "https://localhost:5001";
opt.ClientId = "mvc";
opt.ResponseType = "code id_token";
opt.Scope.Add("openid");
opt.Scope.Add("email");
opt.Scope.Add("office");
opt.ClientSecret = "secret";
opt.SignInScheme = "Cookies";
opt.SaveTokens = false;
});
And the client
new Client
{
ClientId = "mvc",
ClientName= "MVC Demo",
Description = "MVC Demo",
AllowedGrantTypes = GrantTypes.Implicit,
RedirectUris = { "https://localhost:44344/signin-oidc" },
PostLogoutRedirectUris = { "https://localhost:44344/signout-callback-oidc" },
BackChannelLogoutUri = "https://localhost:44344/signout-oidc",
FrontChannelLogoutUri = "https://localhost:44344/signout-oidc",
AllowedScopes = { "openid", "email", "office", "profile" },
}
Logging into to the client works absolutely fine, but the issue arises when I want to add a hybrid flow (following the Microsoft academy video from 2019 on it) and it simply isn't working. It's giving me the error code challenge required when logging in - I'm trying to update it so that it can call the API from the MVC app
This is what I update:
I add the scope to the "oidc" scheme, and set the client secret
opt.Scope.Add("api1");
opt.ClientSecret = "secret";
And against the client I add the client secret, add api1 to the allowed scopes, and update the flow to hybrid
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedScopes = { "openid", "email", "office", "profile", "api1" },
AllowedGrantTypes = GrantTypes.Hybrid,
From then, when I click login I get this
I've spent the last 2 days trying to figure out what I'm missing from this

The problem is that IdentityServer expects that you use the PKCE extension and to disable it you can set the RequirePkce parameter to false. However, for security reasons you should try to use this feature.
It says here:
RequirePkce
Specifies whether clients using an authorization code based grant type must send a proof key (defaults to true).
Also, it's recommended not to use the Hybrid flow. Do read this article for more details.
In the source code here, it says:
/// <summary>
/// Enables or disables the use of the Proof Key for Code Exchange (PKCE) standard.
/// This only applies when the <see cref="ResponseType"/> is set to <see cref="OpenIdConnectResponseType.Code"/>.
/// See https://tools.ietf.org/html/rfc7636.
/// The default value is `true`.
/// </summary>
public bool UsePkce { get; set; } = true;
So, its disabled for hybrid flow?

Related

Why does Identity Server GetLogoutContextAsync() method always returns null for PostLogoutRedirectUri?

Identity Server is working as expected. I can log user in and log user out. However the PostLogoutRedirectUri property on LogoutRequest object is always coming back as null.
My SPA client configuration:
{
ClientId = "pokemon",
ClientName = "Angular Pokemon Client",
AllowedGrantTypes = GrantTypes.Code,
RequireClientSecret = false,
RedirectUris = { "http://localhost:4200/login" },
PostLogoutRedirectUris = { "http://localhost:4200" },
AllowedCorsOrigins = { "http://localhost:4200" },
AllowOfflineAccess = true,
AllowAccessTokensViaBrowser = true,
AllowRememberConsent = false,
RequireConsent = true,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"scope1"
}
}
The settings for AccountOptions object are:
public static bool AllowLocalLogin = true;
public static bool AllowRememberLogin = true;
/.../
public static bool ShowLogoutPrompt = false;
public static bool AutomaticRedirectAfterSignOut = true;
Then on the client I am using the oidc-client library. I have the following settings configured:
const settings = {
authority: "https://localhost:5001",
client_id: "pokemon",
redirect_uri: "http://localhost:4200/login",
response_type: "code",
scope:"openid profile scope1",
userStore: new WebStorageStateStore({ store: window.localStorage })
}
I have tried with post_logout_redirect_uri value and without. Same result.
The way I make the logout request is this.mgr.signoutRedirect(). I have also tried with adding this.mgr.signoutRedirect({ id_token_hint: user.id_token }) but got same result.
The first request going out of my client to the IdP has the following URL
https://localhost:5001/connect/endsession?id_token_hint=eyJhbGciOiJSUzI1NiIsImtpZCI6IjhEQTE2MDdBRTE2NzJGODQ3RkU2NkE2MUI2NEFGM0IxIiwidHlwIjoiSldUIn0.eyJuYmYiOjE2MDE1ODMxODQsImV4cCI6MTYwMTU4MzQ4NCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMSIsImF1ZCI6InBva2Vtb24iLCJpYXQiOjE2MDE1ODMxODQsImF0X2hhc2giOiJRajc0Z1Z6VGc5WUd3OGVhaTlKWDhRIiwic19oYXNoIjoiM1k2NGtROVFsY2d3Q0VUSGpMT1RDQSIsInNpZCI6IkRFQkFERTA1Njg5RTk1RDY0NUQwNUJGOTkyREJCRTBDIiwic3ViIjoiYWxpY2UiLCJhdXRoX3RpbWUiOjE2MDE1ODMxNjIsImlkcCI6ImxvY2FsIiwiYW1yIjpbInB3ZCJdfQ.xpQo3SFT_Pc4LDtXPHWEETkweLmevUQvPj_84EC98s8qy272mb1dIc3rsIxpHvmBy6f4kI3z4CRs0w6fZmLGyWtZCYCcM6RJhKyGIz_epr-s_ZfZ7XE9Fwvy2FWFZ_HL0SgqLyUCwxKyel0GnzgEmHqcgIbKrK-3KAsVVuNKbXfEwCE-HsVv0OPssAmWvqRdN61ZtbIst4LP6TISkTvlP8HNZozlpbVawGjRPeubyImoYCZgPDVBYI3Ml_xtmSRITdIcTT9S8JmGL4sBIzNXW2ChOTuMvcEkix2lmPH1e9orFA2QOdGgeHylv6sza5ukHR6HTIF9ypoMon-ycNBPJw
Then the second request is fired
https://localhost:5001/Account/Logout?logoutId=CfDJ8CU-F4FvYn9IkMAT1M74c9qWz8pFpIUH_9uKhIkfUFRQKmkVvPVyRNSRpMnTTQ2ZjIqEqFONFzQ6334fLzoKrrUoxjfnIEXYONgXLCnB3IL0OGjaQcP2WIeX-u7UAx_7LIs-DRvGiDEsgnrfhveZknsDPPcJvediQ3viec63gA9EGo5g467Hcd_JClsdikFAd3j2daTxAdVvhmzmjW60ghfibOnsERghDz3FuuX0vDMjBo5JsRyFQeM78BNnvHkoMOIunz2m4RpJLHHzApRxz0Dofl3Oa9JsVxISGevK02Be1W0oTp1eUh_Yb2a6rMYmkhR2vUg4_MazHi61NI5Lvg1X2gn8x3HR2SiKO6-BEiNK07Mt1poyky4A31DcIQiJKQ
On the Identity Server provider, looking at the logs, there are no errors or warning. This code then gets executed:
// get context information (client name, post logout redirect URI and iframe for federated signout)
var logout = await _interaction.GetLogoutContextAsync(logoutId);
var vm = new LoggedOutViewModel
{
AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName,
SignOutIframeUrl = logout?.SignOutIFrameUrl,
LogoutId = logoutId
};
and logout?.PostLogoutRedirectUri is always returning null. logoutId has a value. Inspecting source code for GetLogoutContextAsync seems to simply take the logoutId and deserialize it in Message object.
When I manually change PostLogoutRedirectUri to http://localhost:4200 it works. Any ideas why it keeps returning null?
In the logout function on the client side, use the following :-
this._userManager.signoutRedirect({'id_token_hint' : this._user.id_token});
where _user is the returned "User" object. Please refer IdentityServer4 logout as the resolution already exists.
Also, go to the identity server log and check if you get a similar log and observe the call to the session endpoint (the ClientId and the "PostLogOutUri" is null apart from other parameters)

Identity server 4. Identity resource claims in resource owner password flow

I'm using resource owner password grand type. I implemented 2 interfaces: IResourceOwnerPasswordValidator and IProfileService. When I request a token, I get to the ResourceOwnerPasswordValidator, after successful validation I expect ProfileService provides me with requested climes. But it never does.
The thing is the RequestedClaimTypes from ProfileDataRequestContext in the ProfileService is always empty, as a result I can't filter out claims which weren't asked.
I expect it contains all claims, which are related to the requested scopes.
For example if I request such scopes as email or "profile" then I expect claims like "email", "first_name", "preferred_username" and others to be in the RequestedClaimTypes list.
There is simple clients and identity resource config:
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email()
};
...........................................................................
new Client
{
ClientId = "client",
ClientName = "SomeClient",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowOfflineAccess = true,
UpdateAccessTokenClaimsOnRefresh = true,
RefreshTokenUsage = TokenUsage.ReUse,
SlidingRefreshTokenLifetime = 1200,
RefreshTokenExpiration = TokenExpiration.Sliding,
AccessTokenLifetime = 900,
IdentityTokenLifetime = 120,
AllowedGrantTypes = { GrantType.ResourceOwnerPassword },
AccessTokenType = AccessTokenType.Jwt,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email
}
},
Interesting fact is that RequextedResource from ProfileDataRequestContext has claims data, which I can use to figure out what claims user has requested. For example:
var claimsNames = new List<string>();
claimsNames.AddRange(context.RequestedResources.IdentityResources.SelectMany(r => r.UserClaims));
claimsNames.AddRange(context.RequestedResources.ApiResources.SelectMany(r => r.UserClaims));
context.RequestedClaimTypes = claimsNames;
But it seems to be hacky way. Does someone know why RequestedClaimTypes isn't populated automatically?
P.S Client is the postman with the following parameters

IdentityServer/OWIN: Client not being redirected to Login page after Logout

I have a new IdP that implements IdentityServer4 (.NET Core). I am using it to provide SSO/Cookie authentication/authorization to an MVC5 client app. Since the client app is not .NET Core, I use the IdentityServer3 and Microsoft.Owin nugets in order to integrate. There aren't tons of examples of mixing .NET Core and .NET together like this, but there are a few and I've done my best to make it work. Here is the configuration source code for each:
IdentityServer4 (.NET Core, based largely on this example):
new Client()
{
ClientId = "myClientId",
ClientName = "My Client",
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
Enabled = true,
RedirectUris = { "http://localhost:5002/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
}
}
My Client (MVC5):
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "http://localhost:5000",
ClientId = "myClientId",
RedirectUri = "http://localhost:5002/signin-oidc",
PostLogoutRedirectUri = "http://localhost:5002/signout-callback-oidc",
ResponseType = "code id_token",
SignInAsAuthenticationType = "Cookies",
Scope = "openid",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = n => {
n.OwinContext.Response.Cookies.Append("stored_id_token", n.ProtocolMessage.IdToken);
return Task.FromResult(0);
},
RedirectToIdentityProvider = n => {
if (n.ProtocolMessage.RequestType == Microsoft.IdentityModel.Protocols.OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.Request.Cookies["stored_id_token"];
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint;
var signOutMessageId = n.OwinContext.Environment.GetSignOutMessageId(); // returns NULL!
//var signOutMessageId = n.OwinContext.Request.Query.Get("id"); // same thing as line above
if (signOutMessageId != null)
{
n.ProtocolMessage.State = signOutMessageId;
}
}
}
return Task.FromResult(0);
}
}
});
Logout mostly works ok. The issue I have is with redirecting back to the login page so that the enduser can login again to the client they just logged out of via the link highlighted here:
The RedirectUri is configured correctly on both sides of configuration (IdP and Client), and the hyperlink's href value is set in the View (pictured), but that URL also needs a query param called "state" attached to it so that it knows which application to log back into (eg. http://localhost:5002/signout-callback-oidc?state=123456789 Clicking this link without the "state" param gives you a 404.). The issue I'm having is that I'm unable to get/set this "state" value in "My Client".
From everything I've read, you get it by calling n.OwinContext.Environment.GetSignOutMessageId() which is actually just calling n.OwinContext.Request.Query.Get("id") under the covers, but it always returns null. Any idea why this returns null??
Thank you!

Sharing Access Token Between Clients

I have an application that authenticates with Identity Server 4 using implicit flow. The application is separated into two separate .net core applications. One application handles the back end and the other handles serving the front end.
The back end and front end share the same scopes, api name, and authority settings.
I will be writing an integration with a 3rd party. Our back end application will call into the 3rd party application. We need to ensure that calls to the 3rd party application are authenticated. I'd like to share the access token the back end application receives from the front end and send that to the 3rd party application. I am not quite sure of the setup required to do this.
I thought that I could add a new Client to Identity Server and set it up with only the needed scopes required by the 3rd party. But in my local testing, I haven't been able to get this to work. I get an error IDX10804 Unable to obtain configuration from .../.well-known/openid-configuration - A security error occurred.
My config looks like:
new Client
{
ClientId = "thirdPartyClient",
ClientName = "thirdPartyClient",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email
},
AllowOfflineAccess = true,
AllowAccessTokensViaBrowser = true,
RequireConsent = false
},
new Client
{
ClientId = "myapplication",
ClientName = "myapplication",
AllowedGrantTypes = GrantTypes.Implicit,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "https://.../callback.html" },
PostLogoutRedirectUris = { "https://.../index.html"
AllowedCorsOrigins = { "https://..." },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Address,
"myscope",
},
AllowOfflineAccess = true,
AllowAccessTokensViaBrowser = true,
RequireConsent = false
}
Am I going about this the right way?
You haven't added redirect uri for the client thirdPartyClient. Without redirect uri client won't be valid.
RedirectUris
Specifies the allowed URIs to return tokens or authorization codes to
If you turn on detailed logging with Disable Just My code. You could see the error logs to find the actual reason

IdentitySever4 user claims and ASP.NET User.Identity

I've written a small IdentityServer demo server, following the examples in the documentation. I have the following TestUser:
new TestUser
{
SubjectId = "1",
Username = "Username",
Password = "password",
Claims = new List<Claim>()
{
new Claim(System.Security.Claims.ClaimTypes.Name, "Username"),
new Claim(System.Security.Claims.ClaimTypes.Email, "username#domain.com")
}
}
I get an access token using ResourceOwnerPassword flow. And I am authorized to access my API.
The problem is that when in my protected API I'm trying to get the user identity, the name property is returned as null, and I don't see the email claim. No matter what I do I always see the same 12 claims. The sub claim is the only one passed with the information I put in the Client object.
How can I populate the HttpContext.User.Identity.Name property and send additional claims/data about the user?
The reason probably is that you are not requesting the proper resources/scopes for your client.
You need to define an API resource with the claims you need in the access token.
e.g in Resources.cs you can add the claims to be included in all api2 scopes
new ApiResource
{
Name = "api2",
ApiSecrets =
{
new Secret("secret".Sha256())
},
UserClaims =
{
JwtClaimTypes.Name,
JwtClaimTypes.Email
},
Scopes =
{
new Scope()
{
Name = "api2.full_access",
DisplayName = "Full access to API 2",
},
new Scope
{
Name = "api2.read_only",
DisplayName = "Read only access to API 2"
}
}
}
Then you allow your resource owner client the access to those API resources.
e.g in client.cs
new Client
{
ClientId = "roclient",
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AllowOfflineAccess = true,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
"custom.profile",
"api1", "api2.read_only"
}
},
You can then request the scope in your roclient
client.RequestResourceOwnerPasswordAsync("bob", "bob", "api2.read_only", optional).Result
Post the access token to the API and you will get the claims you added to your API resource.
In the call to UseOpenIdConnectAuthentication, or wherever you're trying to use the token, make sure you set the TokenValidationParameters for the Name property to ClaimTypes.Name.
By default, the Name claim type is set to name (JwtClaimType.Name).

Resources