How to validate Identityserver4 token validation in webapi? - identityserver4

Have you tried to ID4 connect with web api (.net framework 4.6) I follow below the tutorial but APIResource with secret key is not working. it also not giving any error if I give wrong API resource name and secret.
https://nahidfa.com/posts/identityserver4-and-asp-.net-web-api/
Source code
var IDSBearerOption = new IdentityServerBearerTokenAuthenticationOptions
{
AuthenticationType = "Bearer",
Authority = "https://localhost:5000",
ValidationMode = ValidationMode.Local,
RequiredScopes = new[] { "api1" },
PreserveAccessToken = true,
RoleClaimType = "role",
ValidAudiences = new[] { "TestAPI1" } ,
ClientId = "TestAPI1", //api resource name
ClientSecret = "secret1" //api resource secret
};
app.UseIdentityServerBearerTokenAuthentication(IDSBearerOption);
Is it possible to validate the token in webapi .net framework4.6?

You'll have to share cookies and set your bearer token auth options inside OWIN in startup:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
CookieHttpOnly = false,
CookieSecure = CookieSecureOption.Always,
CookieName = "MySharedCookieName"
});
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = authorityUrl,
RequiredScopes = new[] {"MyAPIName"},
ClientId = "MyAPIName",
ClientSecret = "secret",
AuthenticationType = "Bearer",
NameClaimType = "name",
RoleClaimType = "role",
SigningCertificate = x509Cert
});
Just make sure your cookie names match in IDS4 and all other apps

Related

Getting a Refresh Token from IdentitySever4

I have a Blazor web app that connects to a different Identity Server 4 server. I can get the login to work correctly and pass the access token back the Blazor. However, when the token expires I don't know how to go out and get a new access token? Should I be getting a refresh token and then an access token? I am confused on how this all works.
Blazor Code
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = AzureADDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(AzureADDefaults.AuthenticationScheme, options =>
{
options.Authority = "https://localhost:44382";
options.RequireHttpsMetadata = true;
options.ClientId = "client";
options.ClientSecret = "secret";
options.ResponseType = "code id_token token";
options.SaveTokens = true;
options.Scope.Add("IdentityServerApi");
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("roles");
options.Scope.Add("offline_access");
});
IdentityServer4 Setup
...
new Client
{
ClientId = "client",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Hybrid,
AllowAccessTokensViaBrowser = true,
RequireClientSecret = true,
RequireConsent = false,
RedirectUris = { "https://localhost:44370/signin-oidc" },
PostLogoutRedirectUris = { "https://localhost:44370/signout-callback-oidc" },
AllowedScopes = { "openid", "profile", "email", "roles", "offline_access",
IdentityServerConstants.LocalApi.ScopeName
},
AllowedCorsOrigins = { "https://localhost:44370" },
AlwaysSendClientClaims = true,
AlwaysIncludeUserClaimsInIdToken = true,
AllowOfflineAccess = true,
AccessTokenLifetime = 1,//testing
UpdateAccessTokenClaimsOnRefresh = true
},
...
UPDATE:
I have updated my code to offline_access for the client and server (thanks for the update below). My next question is how do I inject the request for the refresh token in Blazor once I get rejected because the access token is expired?
I have the Blazor app making calls back to the API (which validates the access token).
public class APIClient : IAPIClient
{
private readonly HttpClient _httpClient;
//add the bearer token to the APIClient when the client is used
public APIClient(IHttpContextAccessor httpAccessor, HttpClient client, IConfiguration configuration)
{
var accessToken = httpAccessor.HttpContext.GetTokenAsync("access_token").Result;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestVersion = new Version(2, 0);
client.BaseAddress = new Uri(configuration["Api_Location"]);
_httpClient = client;
_logger = logger;
}
What do I need to add to my API calls to validate?
Yes, you should obtain a refresh token as well to keep getting new access tokens. To get a refresh token from IdentityServer you need to add the 'offline_access' scope in the 'AllowedScopes' property of your client. You also need to set the 'AllowOfflineAccess' property on your client to true.
After that you need to include 'offline_access' to the scopes sent by the client and you should receive a refresh token in the response.
To use the refresh token, send a request to the token endpoint with everything you sent for the code exchange except replace the 'code' param with 'refresh_token' and change the value for 'grant_type' from 'code' to 'refresh_token'. The IdentityServer4 response to this request should contain an id_token, an access_token, and a new refresh_token.
I think I have found an answer (given the push from Randy). I did something familiar to this post, where I created a generic method in my APIClient.
public async Task<T> SendAsync<T>(HttpRequestMessage requestMessage)
{
var response = await _httpClient.SendAsync(requestMessage);
//test for 403 and actual bearer token in initial request
if (response.StatusCode == HttpStatusCode.Unauthorized &&
requestMessage.Headers.Where(c => c.Key == "Authorization")
.Select(c => c.Value)
.Any(c => c.Any(p => p.StartsWith("Bearer"))))
{
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "refresh_token"),
new KeyValuePair<string, string>("refresh_token", _httpAccessor.HttpContext.GetTokenAsync("refresh_token").Result),
new KeyValuePair<string, string>("client_id", "someclient"),
new KeyValuePair<string, string>("client_secret", "*****")
};
//retry do to token request
using (var refreshResponse = await _httpClient.SendAsync(
new HttpRequestMessage(HttpMethod.Post, new Uri(_authLocation + "connect/token"))
{
Content = new FormUrlEncodedContent(pairs)})
)
{
var rawResponse = await refreshResponse.Content.ReadAsStringAsync();
var x = Newtonsoft.Json.JsonConvert.DeserializeObject<Data.Models.Token>(rawResponse);
var info = await _httpAccessor.HttpContext.AuthenticateAsync("Cookies");
info.Properties.UpdateTokenValue("refresh_token", x.Refresh_Token);
info.Properties.UpdateTokenValue("access_token", x.Access_Token);
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", x.Access_Token);
//retry actual request with new tokens
response = await _httpClient.SendAsync(new HttpRequestMessage(requestMessage.Method, requestMessage.RequestUri));
}
}
if (typeof(T).Equals(typeof(HttpResponseMessage)))
return (T)Convert.ChangeType(response, typeof(T));
else
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(await response.Content.ReadAsStringAsync());
}
I don't like that I have to call AuthenticateAsync. Yet, that seems to be the way I have found to get access to the UpdateTokenValue method to delete and then re-add the new access token.

Identity Server 4 Checking for expected scope openid failed

I'm trying to set up an Identity Server for the first time in ASP.NET Core. I've set up everything to use a database and have created a script to create a test client, test user and resources. I can request a client token and request a user token, those work fine, but when calling the connect/userinfo endpoint, I'm getting a Forbidden response and the following error;
IdentityServer4.Validation.TokenValidator[0]
Checking for expected scope openid failed
{
"ValidateLifetime": true,
"AccessTokenType": "Jwt",
"ExpectedScope": "openid",
"Claims": {
"nbf": 1556641697,
"exp": 1556645297,
"iss": "https://localhost:5001",
"aud": [
"https://localhost:5001/resources",
"customAPI"
],
"client_id": "newClient",
"sub": "75f86dd0-512e-4c9d-b298-1afa120c7d47",
"auth_time": 1556641697,
"idp": "local",
"role": "admin",
"scope": "customAPI.read",
"amr": "pwd"
}
}
I'm not sure what is causing the issue. Here is the script I used to setup the test entities;
private static void InitializeDbTestData(IApplicationBuilder app)
{
using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
scope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
scope.ServiceProvider.GetRequiredService<ConfigurationDbContext>().Database.Migrate();
scope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
var context = scope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
// API Client
Client client = new Client
{
ClientId = "newClient",
ClientName = "Example Client Credentials Client Application",
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
ClientSecrets = new List<Secret>
{
new Secret("123456789".Sha256())
},
AllowedScopes = new List<string> {"customAPI.read"}
};
context.Clients.Add(client.ToEntity());
context.SaveChanges();
// Identity Resources
IList<IdentityResource> identityResources = new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResource
{
Name = "role",
UserClaims = new List<string> {"role"}
}
};
foreach (IdentityResource identityResource in identityResources)
{
context.IdentityResources.Add(identityResource.ToEntity());
}
// API Resource
ApiResource resource = new ApiResource
{
Name = "customAPI",
DisplayName = "Custom API",
Description = "Custom API Access",
UserClaims = new List<string> {"role"},
ApiSecrets = new List<Secret> {new Secret("scopeSecret".Sha256())},
Scopes = new List<Scope>
{
new Scope("customAPI.read"),
new Scope("customAPI.write")
}
};
context.ApiResources.Add(resource.ToEntity());
context.SaveChanges();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>();
// User
IdentityUser user = new IdentityUser
{
UserName = "JohnDoe",
Email = "john#doe.co.uk",
};
IList<Claim> claims = new List<Claim>
{
new Claim(JwtClaimTypes.Email, user.Email),
new Claim(JwtClaimTypes.Role, "admin")
};
userManager.CreateAsync(user, "112222224344").Wait();
userManager.AddClaimsAsync(user, claims).Wait();
}
}
I'm sure I've set up something wrong when I set up the client/user, can anyone pinpoint what it is?
Can't see your client side code, but the error says you did not requested openid scope when applied for the token. The token valid for Useinfo endpoint must contain openid scope.

How to create JWT token using IdentityServer4

In my application (.Net core application) using IdentityServer4, at present creates "Reference" Token for authentication. But I would need to change the token type from "Reference" type to "JWT" token. I found couple of articles regarding that and tried as mentioned, but still I am not able to get the "JWT" token and I am getting "Reference" token only.
I followed the details mentioned in the below sites, but no luck.
IdentityServer4 requesting a JWT / Access Bearer Token using the password grant in asp.net core
https://codebrains.io/how-to-add-jwt-authentication-to-asp-net-core-api-with-identityserver-4-part-1/
https://andrewlock.net/a-look-behind-the-jwt-bearer-authentication-middleware-in-asp-net-core/
Can anyone let me know how could we change the token type from "Reference" to "JWT" token? Is there any custom code/class to be created to achieve this?
Below is the code used in my Client class.
new Client
{
ClientId = "Client1",
ClientName = "Client1",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AllowedScopes = new List<string>
{
IdentityScope.OpenId,
IdentityScope.Profile,
ResourceScope.Customer,
ResourceScope.Info,
ResourceScope.Product,
ResourceScope.Security,
ResourceScope.Sales,
ResourceScope.Media,
ResourceScope.Nfc,
"api1"
},
AllowOfflineAccess = true,
AlwaysSendClientClaims = true,
UpdateAccessTokenClaimsOnRefresh = true,
AlwaysIncludeUserClaimsInIdToken = true,
AllowAccessTokensViaBrowser = true,
// Use reference token so mobile user (resource owner) can revoke token when log out.
// Jwt token is self contained and cannot be revoked
AccessTokenType = AccessTokenType.Jwt,
AccessTokenLifetime = CommonSettings.AccessTokenLifetime,
RefreshTokenUsage = TokenUsage.OneTimeOnly,
RefreshTokenExpiration = TokenExpiration.Sliding,
AbsoluteRefreshTokenLifetime = CommonSettings.AbsoluteRefreshTokenLifetime,
SlidingRefreshTokenLifetime = CommonSettings.SlidingRefreshTokenLifetime,
IncludeJwtId = true,
Enabled = true
},
And in my startup.cs, I have this below code.
public void ConfigureServices(IServiceCollection services)
{
var connStr = ConfigurationManager.ConnectionStrings[CommonSettings.IDSRV_CONNECTION_STRING].ConnectionString;
services.AddMvc();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
// base-address of your identityserver
options.Authority = "http://localhost:1839/";
// name of the API resource
options.Audience = "api1";
options.RequireHttpsMetadata = false;
});
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
}
);
var builder = services.AddIdentityServer(options => setupAction(options))
.AddSigningCredential(loadCert())
.AddInMemoryClients(Helpers.Clients.Get())
.AddInMemoryIdentityResources(Resources.GetIdentityResources())
.AddInMemoryApiResources(Resources.GetApiResources()).AddDeveloperSigningCredential()
.AddConfigStoreCache().AddJwtBearerClientAuthentication()
//Adds a key for validating tokens. They will be used by the internal token validator and will show up in the discovery document.
.AddValidationKey(loadCert());
builder.AddConfigStore(options =>
{
//CurrentEnvironment.IsEnvironment("Testing") ?
// this adds the config data from DB (clients, resources)
options.ConfigureDbContext = dbBuilder => { dbBuilder.UseSqlServer(connStr); };
})
.AddOperationalDataStore(options =>
{
// this adds the operational data from DB (codes, tokens, consents)
options.ConfigureDbContext = dbBuilder => { dbBuilder.UseSqlServer(connStr); };
// this enables automatic token cleanup. this is optional.
options.EnableTokenCleanup = true;
options.TokenCleanupInterval = CommonSettings.TokenCleanupInterval;
});
}
Kindly let me know, what change(s) to be done to get JWT token. Thanks in advance.

Ca not get Claims information while login Azure active B2B directory

I can not get ClaimsPrincipal after login in azure Ad Web API,
Below is my code added in startup.auth.cs
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Scope= OpenIdConnectScope.OpenIdProfile,
ResponseType = OpenIdConnectResponseType.IdToken,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
return Task.FromResult(0);
}
},
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = false
},
});
I get Access Token in
result = await authContext.AcquireTokenAsync(todoListResourceId, clientCredential);
but can not get ClaimsPrincipal. I get AuthenticationType = null, IsAuthenticated = null, Name = null.
My application use adal.js for UI side to get user information, and get user information successfully.
I got Solution for this problem.replace startup.auth.cs code with
app.UseWindowsAzureActiveDirectoryBearerAuthentication( new WindowsAzureActiveDirectoryBearerAuthenticationOptions { TokenValidationParameters = new TokenValidationParameters { SaveSigninToken = true, ValidAudience = ConfigurationManager.AppSettings["ida:ClientId"], AuthenticationType = "Bearer" }, Tenant = ConfigurationManager.AppSettings["ida:Tenant"], });
this code and it working fine

Connecting IdentityServer4 with Azure Active Directory

As one of my requirements, I am supposed to connect the IdentitySever with an Active Directory with existing users and claims. So far I managed to create an App Registration in the Azure Portal. So I have an Appication ID and also configured an API Key. Further, I have a list of Endpoints:
https://login.windows.net/{ad_guid}/federationmetadata/2007-06/federationmetadata.xml
https://login.windows.net/{ad_guid}/wsfed
https://login.windows.net/{ad_guid}/saml2
https://login.windows.net/{ad_guid}/saml2
https://graph.windows.net/{ad_guid}
https://login.windows.net/{ad_guid}/oauth2/token
https://login.windows.net/{ad_guid}/oauth2/authorize
I can get the OpenID configuration with
https://login.windows.net/{ad_guid}/.well-known/openid-configuration
According to the documentation from Microsoft I should now configure the endpoint like this:
app.SetDefaultSignInAsAuthenticationType(
CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
var uri = "https://login.windows.net/{0}";
var instance = configuration["AzureAD:Instance"];
var authority = string.Format(CultureInfo.InvariantCulture, uri, instance);
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
DisplayName = "Azure Active Directory",
AuthenticationScheme = "AzureAD",
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
ClientId = configuration["AzureAD:AppId"],
Authority = authority,
Scope = {"openid", "email"}
});
For some reason this is not working. Any ideas what I might have missed?
apparently, I had it almost right. here is my solution:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme =
IdentityServerConstants.DefaultCookieAuthenticationScheme,
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
public static OpenIdConnectOptions CreateAzureAdOptions(X509Certificate2 certificate2, IConfiguration configuration)
{
return new OpenIdConnectOptions
{
DisplayName = "Azure Active Directory",
AuthenticationScheme = "Azure",
ClientId = configuration["OpenId:AzureAD:AppId"],
Authority = string.Format(CultureInfo.InvariantCulture, "https://login.windows.net/{0}", configuration["OpenId:AzureAD:Instance"]),
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false
},
// https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-token-and-claims
Scope = {"openid", "email", "roles", "groups"},
Events = new OpenIdConnectEvents
{
OnRemoteFailure = context => HandleRemoteFailure(context)
},
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme
};
}
private static Task HandleRemoteFailure(FailureContext context)
{
Log.Error(context.Failure, "Azure AD Remote Failure");
context.Response.Redirect("/accessdenied");
context.HandleResponse();
return Task.FromResult(0);
}

Resources