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.
Related
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
I created project from template: "is4aspid" and added Azure Active Directory authentication.
In Startup.cs added this code:
// preserve OIDC state in cache (solves problems with AAD and URL lenghts)
services.AddOidcStateDataFormatterCache();
// cookie policy to deal with temporary browser incompatibilities
services.AddSameSiteCookiePolicy();
services.AddAuthentication()
.AddOpenIdConnect("aad", "Sign-in with Azure AD", options =>
{
options.Authority = "https://login.microsoftonline.com/common";
options.ClientId = "myClientId";
options.ClientSecret = "myClientSecret";
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.ResponseType = "id_token";
options.CallbackPath = "/signin-aad";
options.SignedOutCallbackPath = "/signout-callback-aad";
options.RemoteSignOutPath = "/signout-aad";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
NameClaimType = "name",
RoleClaimType = "role"
};
});
I added redirect uri on Azure: http://localhost:5000/signin-aad
Error occure where Callback method is called.
HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme) Success is always false
[HttpGet]
public async Task<IActionResult> Callback()
{
var result = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
// some code
}
Where is my error?
Solution
Must define IdentityConstants.ExternalScheme in startup and callback
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.
I am implementing EF for Identity framework 4 so that i can store tokens in database. My question is, does it store code, Access token and Refresh token all in database (http://docs.identityserver.io/en/latest/reference/ef.html).
After i implemented code from the document link, that i pasted above, i am getting refresh token data in database but not access token. Also when i try to get a new access token from refresh token, i get new refresh token as well for which i dont see a new entry in database (PersistedGrants) table.
StartUp:
services.AddIdentityServer()
.AddDeveloperSigningCredential(filename: "key.rsa")
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString);
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationAssembly));
// this enables automatic token cleanup. this is optional.
options.EnableTokenCleanup = true;
options.TokenCleanupInterval = 30; // interval in seconds
})
.AddConfigurationStoreCache()
Client:
ClientId = "testclient",
ClientName = "testclient",
ClientSecrets = { "password" },
AllowedGrantTypes = GrantTypes.Implicit,
RequireConsent = false,
RedirectUris = { "https://testapp.azurewebsites.net/signin-oidc"},
PostLogoutRedirectUris = { "https://testapp.azurewebsites.net/signout-callback-oidc" },
FrontChannelLogoutUri = "https://testapp.azurewebsites.net/FrontChannelLogout",
//FrontChannelLogoutUri = "https://testapp.azurewebsites.net/signout-callback-oidc",
AllowedScopes = new List<string>
{
"OpenId",
"Profile",
},
AllowOfflineAccess = true
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