IdentityServer4 ClientCredentials basic question (I think) - c#

This is probably a basic question about IdentityServer4
So Im working through the identityserver4 docs
Ive done the
[Protecting an API using Client Credentials] https://docs.identityserver.io/en/latest/quickstarts/1_client_credentials.html#
I get that so I set up an APi Resource with an Api Scope (api1) and my "Client" uses Client Credentials and that scope
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
}
};
ok so then I do the next section "Interactive Applications with ASP.NET Core"
and I get that
So my Client will need to do both so happily there is the next section
"ASP.NET Core and API access" - to bring them both together which says all I have to do is
new Client
{
ClientId = "mvc",
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" },
AllowOfflineAccess = true,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
which does work
What I dont understand is - it is the IdentityServer4 client "client" that has the
AllowedGrantTypes = GrantTypes.ClientCredentials
so how does "mvc" Client only have to add "api1" to the allowed scope to get the Client Credentials?
I cant see an explanation in the docs
is a bearer token by convention "Client Credentials" (so I dont need the Client "client" anymore) ? or does IdentityServer4 somehow link the Clients based on the fact they both have the api1 scope?
Thanks

ClientCredentials, is for service-to-service communication. Perhaps you have a background job that needs to talk to an API. For for one API to talk to another API.
The authorization code flow is typically for MVC application where you have a user logging in to the site and you get back an access token that you can then send to a given API, like API1 in this case.
In your scenario you don't really need to use both. Only the authorization code flow is enough for the public facing server with api1 as the scope.
You use bearer tokens (authorization header) in both flows when you talk to an API.

Related

How do I get id_token for keycloak using OWIN?

I'm working with an very old VB.net application trying to layer in SSO auth using OWIN and KeyCloak. This is all new to me. The approach I'm taking is to create a C# app to sit in between KeyCloak and my VB app. I've been able to get my C# app to open the login screen of KeyCloak, authenticate and return to the C# app or even the VB app. This seems fine.
However, I need the id_token and username to pass to the VB app. When using Fiddler I can see KeyCloak is generating a post back to my return page with the id_token in tow. However, it is on another thread and gets redirected to the original page but without the id_token. I must be missing something. I've seen code where there are notifications wired and I think they should grab the token and user info, but I don't know how to get the notifications to work. There is no explicit documentation to tell me what to do.
Am I supposed to have a listener or callback method to catch the post from KeyCloak? If so can some one show me how to create one?
Note: I've found some Microsoft code using OWIN and Azure and MVC that bring back user info. However, I point this same code to KeyCloak it authenticates but no user info is returned.
Any help will be greatly appreciated.
-Thanks
In my Startup.cs file I have the following (I've tried many different variations to no avail):
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(
CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = _clientId,
ClientSecret = _clientSecret,
RequireHttpsMetadata = false,
Authority = _authority,
RedirectUri = _redirectUri,
// PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
PostLogoutRedirectUri = _redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
// ResponseType is set to request the id_token - which contains basic information about the signed-in user
ResponseType = OpenIdConnectResponseType.IdToken,
// ValidateIssuer set to false to allow personal and work accounts from any organization to sign in to your application
// To only allow users from a single organizations, set ValidateIssuer to true and 'tenant' setting in web.config to the tenant name
// To allow users from only a list of specific organizations, set ValidateIssuer to true and use ValidIssuers parameter
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true
},
// OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
SecurityTokenReceived = OnSecurityTokenReceived
}
}
);
}

Identity Server 4 - unauthorized client

I am struggling with basic setup of the Identity Server 4 with Net Core 3.0 and React (but this is almost irrelevant).
I have generated new app by dotnet new react -au Individual, updated dependencies etc, Created config basically copied from the demo server with the following:
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
// JavaScript Client
new Client
{
Enabled = true,
ClientId = "spa",
ClientName = "SPA (Code + PKCE)",
RequireClientSecret = false,
RequireConsent = false,
RedirectUris = { "https://notused" },
PostLogoutRedirectUris = { "https://notused" },
AllowedGrantTypes = GrantTypes.Code,
AllowedScopes = { "openid", "profile", "email", "api" },
AllowOfflineAccess = true,
RefreshTokenUsage = TokenUsage.ReUse
},
};
}
In my startup:
services.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer(o =>
{
o.UserInteraction.ErrorUrl = "/myErrorsHandler";
o.Events.RaiseErrorEvents = true;
o.Events.RaiseFailureEvents = true;
o.Events.RaiseInformationEvents = true;
o.Events.RaiseSuccessEvents = true;
})
.AddInMemoryApiResources(Config.GetApis())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>()
.AddInMemoryClients(Config.GetClients()) ;
Then I am trying in Postman:
and always getting:
{"displayMode":null,"uiLocales":null,"error":"unauthorized_client","errorDescription":"Unknown client or client not enabled","requestId":"0HLPL86NBMDRG:00000001","redirectUri":null,"responseMode":null,"clientId":"spa"}
I really don't understand why this is not working.
The same client on demo server with the same in Postman dialog works without any issues.
UPDATE:
I found this docs: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization?view=aspnetcore-3.0#application-profiles
but I am still not able to get it working.
It recognizes the client, but despite the config (SPA, IdentityServerSPA) throwing:
{"displayMode":null,"uiLocales":null,"error":"invalid_request","errorDescription":"code challenge required","requestId":"0HLPL8VD22382:00000001","redirectUri":"http://localhost:5000/authentication/login-callback?error=invalid_request&error_description=code%20challenge%20required#_=_","responseMode":"query","clientId":"spa"}
UPDATE 2:
It is "working" with client defined in configuration JSON but only with predefined templates as per doc, but it is impossible (or possibility is not documented) to disable PKCE to make it work e.g. with Postman etc.
You're not defining the client_secret. Based on the code you've provided on the client's configuration you did not setup a client secret, so If no client secret is specified, there's no direct way for your client to prove its authenticity to your Authority (IDserver). This is when PKCE comes in handy, at least you can guarantee that same system is doing both requests.
I see you're asking to disable PKCE, that should not be possible (I'm not sure if it can be done but you definitely shouldn't do that) because you're using code authentication grant for an SPA. (which is the current recommended way of doing things)
As an SPA is a non-confidential client (uncapable of keeping a secret secure) this means that any other application could use your client_id spa to make requests to the token endpoint. To prevent this we combine two things:
Redirect URI: this enforces the response code token to be redirected to a previously known address which should be your client (unless using hosts file to suplant your site)
PKCE: a mechanism that aims to guarantee that both /authorize and /token requests come from the same client, so even if someone manages to intercept the code, he/she should not be able to use it in exchange for a token, because not knowing the original secret used in PKCE.
I struggled with the unauthorized_client error for the Resource Owner Password Validation flow in IdentityServer 4 because the grant type password was missing in [dbo].[ClientGrantTypes] for the associated client_id.
I had to insert a new line into the table to fix this error.
INSERT INTO [dbo].[ClientGrantTypes] (
[GrantType]
,[ClientId])
VALUES ('password', X) --where X is value of [dbo].[Clients].[Id] of used client

How to reject token with invalid claims

I have an application ASP.net 4.X it uses UseOpenIdConnectAuthentication for authentication to an Identity server 4 application.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = identityServerUrl,
ClientId = clientId,
Scope = "openid profile testapi",
ClientSecret = secret,
RedirectUri = basePath + "/signin-oidc",
PostLogoutRedirectUri = basePath,
ResponseType = "id_token token code",
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = false,
....................
All of this works fine currently. However I have an issue. If something happens in the Identity server and it does not return all of the claims in the token that i need. I need a way to refuse it without ending in some kind of endless loop of login which i currently have.
I was wondering if there is some place in the middlewear that i can add something like
var needed= claims.FirstOrDefault(c => c.Type == "NeedThisClaim")?.Value;
If its not there then kick it out and refuse the user access. I was thinking of putting it in SecurityTokenValidated but I am not sure that's the best place or not. I am also not sure how to reject the token gracefully.
How do you verify that a token contains the required claims when authenticating an application.

Manually creating and validating a SessionSecurityToken

I have websites running under Microsoft Identity Model federated authentication and recently I've been trying to create an API in one of them and consume it from the other, the basic problem I have with that is that this Identity doesn't have an impersonate option and thus I can't be sure that the call is secure.
Thus I am currently trying to manually generate and pass a token in the headers, this is what I ended up doing on the client side
var claimsPrincipal = new ClaimsPrincipal();
claimsPrincipal.Identities.Add(new ClaimsIdentity());
IClaimsIdentity ci = (claimsPrincipal.Identity as IClaimsIdentity);
ci.Claims.Add(new Claim(ClaimTypes.Name, User.Identity.Name));
var token = FederatedAuthentication.SessionAuthenticationModule.CreateSessionSecurityToken(claimsPrincipal,"Api Test", DateTime.UtcNow, DateTime.UtcNow.AddMinutes(30), true);
using(var client = new WebClient())
{
client.Headers.Add("Authentication-Token",token.Id);
}
But I just can't figure out a way to check if the token is legit in the API.

Verify Access Token - Asp.Net Identity

I'm using ASP.Net Identity to implement external logins. After user logins in with Google I get google's external access token. I then make a second api call to ObtainLocalAccessToken() which trades the external access token for a new local one.
ObtainLocalAccessToken() calls VerifyExternalAccessToken() which verifies the external access token with the provider by manually making http calls and parsing the user_id.
How can I leverage ASP.NET identity to remove the entire method VerifyExternalAccessToken()?
I believe that's what [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)] is for isn't it? I want to decorate ObtainLocalAccessToken() endpoint with that attribute and send the external_access_token in the header ({'Authorization' : 'Bearer xxx' }), and it should populate User.Identity without needing to manually verify the external access token? I believe that’s the purpose, however I cannot get it working. I send a valid external access token from google and it gets rejected with a 401.
I have this line in Startup.Auth btw:
app.UseOAuthBearerTokens(new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(),
AuthorizeEndpointPath = new PathString("/AccountApi/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
});
Alternatively, it is possible to use "/Token" endpoint to trade an external access token for a local one? Which approach is correct?
Studying the implementation by Taiseer Joudeh
the /ExternalLogin endpoint replaces the OWIN Authentication Challenge.
The AngularJS LoginController makes a call to the authService.obtainAccessToken when an externally authenticated user has not been found in Identity Provider:
if (fragment.haslocalaccount == 'False') {
...
}
else {
//Obtain access token and redirect to orders
var externalData = { provider: fragment.provider,
externalAccessToken: fragment.external_access_token };
authService.obtainAccessToken(externalData).then(function (response) {
$location.path('/orders');
It uses the VerifyExternalAccessToken to perform a reverse lookup against Google and Facebook API's to get claim info for the bearer token.
if (provider == "Facebook")
{
var appToken = "xxxxxx";
verifyTokenEndPoint = string.Format("https://graph.facebook.com/debug_token?input_token={0}&access_token={1}", accessToken, appToken);
}
else if (provider == "Google")
{
verifyTokenEndPoint = string.Format("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={0}", accessToken);
}
else
{
return null;
}
If token is found, it returns a new ASP.NET bearer token
var accessTokenResponse = GenerateLocalAccessTokenResponse(user.UserName);
return Ok(accessTokenResponse);
With [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)] the OWIN Middleware uses the external bearer token to access the 3rd party's Cookie and Register a new account (Or find existing).
OWIN Middleware cannot be configured to accept external bearer token instead of local authority tokens. External bearer tokens are only used for Authentication and Registration.

Categories