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
Related
I have an Identity Server 4 instance running at https://localhost:5443/ and a client React.js application running at http://localhost:3000/ and making a reference to the oidc-client library in order to establish the communication. I've been following more or less this article.
The way I've configured the client (in-memory) on the Identity Server is as follows:
new Client
{
ClientId = "react-js-implicit-flow",
ClientName = "Some App name",
ClientUri = "http://localhost:3000",
AllowedGrantTypes = GrantTypes.Implicit,
RequireClientSecret = false,
RedirectUris = { "http://localhost:3000/signin-oidc", },
PostLogoutRedirectUris = { "http://localhost:3000/signout-oidc" },
AllowedCorsOrigins = { "http://localhost:3000" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"weatherapi.read"
},
AllowAccessTokensViaBrowser = true
}
and the way it looks like on the Ract.js app is like this:
In general, everything goes well. I can login and logout from the Identity Server but the issue is that here:
I get no value (it is null) and this stops the Identity server from redirecting me back to the client application right after logout. If I hard code it (http://localhost:3000/signout-oidc) it works. But for some reason it is just not available.
During the logout this is what the Identity Server logs show:
So, no error, no nothing but I still can not navigate back to the client app after logout.
You do not provide an idTokenHint (id token) with your logout request like the following:
const mgr = new Oidc.UserManager(settings);
const signoutUrl = await mgr.createSignoutRequest({id_token_hint: user.id_token});
window.location.href = signOutUrl.url;
//or
await mgr.signoutRedirect({id_token_hint: user.id_token});
//or just
await mgr.signoutRedirect();
//it will try to attach id_token internally
Lack of the token is the reason for Identityserver to skip the post_logout_redirect_url parameter.
Identityserver has to validate the parameter against the client's configuration, but without the token it can't.
What solved the issue for (me thanks to answer by #d_f) was to change something on the client side and more specifically: src/services/userService/signoutRedirect.
I'm doing a proof of concept, introducing OpenId Connect to our suite of products. After much discussion, we decided it would probably be better to have a separate, discreet Javascript application (Vuejs) for each product, rather than include them all in one monolithic SPA.
For this to work, we effectively need Single Sign-On ("SSO"). I have been using IdentityServer 4 as our IDP.
I seem to have had some reasonable success by providing multiple uris to the RedirectUris property of the relevant Client config of the Idp. So, both apps are effectively the same Client.
That does not feel right and I want to verify whether I am doing that correctly.
Here is my config for the Client config that covers the 2 SPAs (that would cover all the SPAs).
new Client
{
ClientName = "Some App",
ClientId = "appsuiteid",
AccessTokenLifetime = 330,// 330 seconds, default 60 minutes
IdentityTokenLifetime = 300,
AllowOfflineAccess = true,
RefreshTokenUsage = TokenUsage.ReUse,
RefreshTokenExpiration = TokenExpiration.Sliding,
UpdateAccessTokenClaimsOnRefresh = true,
RequireClientSecret = false,
AllowedGrantTypes = GrantTypes.Code,
AllowAccessTokensViaBrowser = true,
RedirectUris = new List<string>
{
"http://localhost:8080/authcallback.html", <-- first 2 Uris for first SPA
"http://localhost:8080/silent-refresh.html",
"http://localhost:8090/authcallback.html", <-- second 2 Uris for second SPA
"http://localhost:8090/silent-refresh.html",
},
PostLogoutRedirectUris = new List<string>
{
"http://localhost:8080/signout-callback-oidc",
"http://localhost:8090/signout-callback-oidc",
},
AllowedCorsOrigins = new List<string>
{
$"http://localhost:8080",
$"http://localhost:8090",
},
AllowedScopes = new List<string>
{
IdentityServer4.IdentityServerConstants.StandardScopes.OpenId,
IdentityServer4.IdentityServerConstants.StandardScopes.Profile,
},
ClientSecrets = { new Secret("somepwd".Sha256())}
}
Note, if I configure a separate client for each SPA, it does not work as each SPA gets redirected to the distinct consent page for that client - thus losing the SSO characteristic which we require.
Any guidance on this would be great.
Thanks
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
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!
What is the mechanism for setting a token validation period in Identity Server 4? Can the validation period vary for different tokens?
The Identity Server 4 documentation at http://docs.identityserver.io/en/dev/ shows a AuthorizationEndpoint with a property max_age, which is what I think I want, but the documentation does not really show it it inter-operates with the quickstart code for IdentityServerWithAspNetIdentity.
I have modified the Client object in Config.cs of the IdentityServerWithAspNetIdentity
// OpenID Connect hybrid flow and client credentials client (MVC)
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
RequireConsent = false,
ClientSecrets =
{
new Secret("LynxJournal".Sha256())
},
//RedirectUris = { "http://localhost:5002/signin-oidc" },
//PostLogoutRedirectUris = { "http://localhost:5002" },
RedirectUris = { serverConfig["MvcClientUrl"] + "/signin-oidc" },
PostLogoutRedirectUris = { serverConfig["MvcClientUrl"] },
IdentityTokenLifetime = 3600,
AccessTokenLifetime = 3600,
AuthorizationCodeLifetime = 3600,
AllowedScopes =
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
StandardScopes.OfflineAccess.Name,
"api1"
}
}
This extends the life of the token to one hour where before the defaults seemed to give about 15-20 minutes. I added values for IdentityTokenLifetime, AccessTokenLifetime and AuthorizationCodeLifetime
Token lifetimes are set per client application. This includes both identity and access tokens. See client application entity.
If you are talking about session length this is set by each application upon successful authentication using IdentityServer.