jwt token multi tenancy - jwt

I implemented in my ASP.NET Core project Web API user authorization via token. I create a token at each login and everything seems to work.
My problem is that the app will be multi-tenancy, so I will have many subdomains client-side, e.g.
(Client1.myapp.com, client2.myapp.com, client3.myapp.com)
Server-side my app that manages the bees will accept a parameter that will be the name of the tenant.
Some examples:
apimyapp.com/client1/api/generateToken
apimyapp.com/client2/api/generateToken
apimyapp.com/client3/api/generateToken
Now if I create the token from client1 and I put in a call apimyapp.com/client2/api/users (insert token generated by the client1 in the header, but the call is made for the client2), the token will be validated and the call gets authorized.
Instead, I wish that the token was valid only for the tenant from which it was generated.
In my startup.cs:
app.UseJwtBearerAuthentication(new JwtBearerOptions()
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = _config["Tokens:Issuer"],
ValidAudience = _config["Tokens:Audience"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"])),
ValidateLifetime = true
}
});
and in my controller for generation token:
var userClaims = _userManagerRepository.GetClaims(user);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.GivenName, user.UserName),
new Claim(JwtRegisteredClaimNames.FamilyName, user.UserName),
new Claim(JwtRegisteredClaimNames.Email, user.Email)
}.Union(userClaims);
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _config["Tokens:Issuer"],
audience: _config["Tokens:Audience"],
claims: claims,
expires: DateTime.UtcNow.AddMinutes(90),
signingCredentials: creds
);

You can add a list on keys, Audiences etc like this to the TokenValidationParameters
ValidAudiences = new List<string>
{
"AUDIENCE1",
"AUDIENCE2"
}

Related

When authscheme specified in Authorization header identity server giving errror invalid_token

I am using identitysever for authentication. I am getting access_token from identity server which I am passing in request header in Authorization. If I pass token without authscheme it identity server is validating token but if i add authschme Bearer(as below example) in Authorization header and pass it gives invalid_token
Authorization: Bearer XXXX_token(failing)
Authorization: XXXX_token(Working)
Below is my code for jwt configuration
AddJwtBearer(ApiConstant.AuthScheme.JwtSso, options =>
{
var configuration =configurationService.GetOpenIdConnectAuthConfiguration();
options.RequireHttpsMetadata = false;
options.Authority = configuration.Authority;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = OnMessageReceived,
// OnChallenge = OnChallenge
};
});
My question is why it is erroring with Bearer?

www-authenticate: Bearer error="invalid_token" in .NET 6 preview 7

I am using VS2022 Preview 3.1 .NET 6 Preview 7 for Blazor wasm hosted App.
I am getting www-authenticate: Bearer error="invalid_token"
Value being passed is
authorization: Bearer "token value removed 8_03bxo56jY7o70"
Token decodes successfully using
code in program.cs(using .NET 6 mminimal API model)
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
var Issuer = builder.Configuration["Jwt:Issuer"];
var Audience = builder.Configuration["Jwt:Audience"];
var Key = builder.Configuration["Jwt:Key"];
if (string.IsNullOrEmpty(Issuer))
Issuer = "MyAppName";
if (string.IsNullOrEmpty(Audience))
Audience = "MyAppAudience";
if (string.IsNullOrEmpty(Key))
Key = "MyApplicationKey";
// For example only! Don't store your shared keys as strings in code.
// Use environment variables or the .NET Secret Manager instead.
var sharedKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes("mysupers3cr3tsharedkey!"));
options.TokenValidationParameters = new TokenValidationParameters
{
// Specify the key used to sign the token:
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Key)),
RequireSignedTokens = true,
// Ensure the token hasn't expired:
RequireExpirationTime = true,
ValidateLifetime = true,
// Ensure the token audience matches our audience value (default true):
ValidateAudience = true,
//ValidAudience = "api://default",
ValidAudience = Audience,
// Ensure the token was issued by a trusted authorization server (default true):
ValidateIssuer = true,
//ValidIssuer = "https://{yourOktaDomain}/oauth2/default",
ValidIssuer = Issuer,
ValidateIssuerSigningKey = true,
//https://developer.okta.com/blog/2018/03/23/token-authentication-aspnetcore-complete-guide
};
options.Events = new JwtBearerEvents
{
//https://stackoverflow.com/questions/35586663/how-to-apply-custom-validation-to-jwt-token-on-each-request-for-asp-net-webapi
// OnTokenValidated = AdditionalValidation
//OnTokenValidated = CustomJWTAuthenticationProvider
};
});
towards the bottom
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapHub<ChatHub>("/chathub");
endpoints.MapFallbackToFile("index.html");
});
The problem was that there was double quote surrounding the token that was being sent in header. I end up in that mix-up because i was upgrading my Blazor client to use Blazored local storage library to write to cookie, but using JSRuntime to read that same cookie.
After using Blazored local storage to both write and read it is working fine

Resolve Authentication problem JWT Bearer for .NET Core API

I use .NET Core 3.1 API and I would like to configure a JWT Bear Token.
I have configured a method which generates a token with a ValidateLifetime of one day.
Then, I put [authorize] on my usercontroller method and I tested all with Postman, by using my token and selecting "Bearer Token as Authorisation method "but I don't know why it doesn't work.
I filled out the token generated previously by my GenerateToken method, I tried to enter only the header part of the jwt token, the header/content/signature of the jwt token but it doesn't work.
Would someone have a video or tutorial explaining how to test the "Bear token" as authentication mode?
//example of generated token :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYXJ0aHVyIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZWlkZW50aWZpZXIiOiIxIiwibmJmIjoiMTYxMDI5OTAzMyIsImV4cCI6IjE2MTAzODU0MzMiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbiJ9.E9TnS62nv10gNH8U03OPhK_QrGLEotnS7yjHBvh4i0E
{
var claims = new List<Claim>{
new Claim(ClaimTypes.Name , UserNAME),
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(JwtRegisteredClaimNames.Nbf,new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Exp,new DateTimeOffset(DateTime.Now.AddDays(1)).ToUnixTimeSeconds().ToString())
};
claims.Add(new Claim(ClaimTypes.Role, "Admin"));
var token = new JwtSecurityToken(
new JwtHeader(
new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SECRET_KEY)),
SecurityAlgorithms.HmacSha256
)),
new JwtPayload(claims));
var output = new
{
Accces_Token = new JwtSecurityTokenHandler().WriteToken(token),
UserName = UserNAME
};
return output;
}
//this is my authentication services
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = "jwtBearer";
options.DefaultChallengeScheme = "jwtBearer";
}).AddJwtBearer("jwtBearer", jwtoptions => {
jwtoptions.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = SIGNING_KEY,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5) };
});
//this is my IApplicationBuilder application configure : {
app.UseAuthentication();
app.UseAuthorization();```}
From the code of generating token, SIGNING_KEY is a string, but IssuerSigningKey is a type of SecurityKey. So in the configuration, it need to be changed.
jwtoptions.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SIGNING_KEY)),
};
The way you tested token may be not correct. After generating the token, you put it in the request header. Note the Bearer.

JWT Token .NET Core Identity Server problem

I am trying to secure a .NET 5.0 Web API with OAuth Client Credentials flow.
My Client is requesting a token from the IdentityServer4 instance and supplying it to the API. The API is then returning a 401 error when I access and endpoint. I notice the following header:
WWW-Authenticate header contains Bearer error=\"invalid_token\", error_description=\"The audience 'empty' is invalid\"
Which suggests my JWT does not contain the audience paramater.
My JWT request code looks like the following:
var tokenResponseType = await serverClient.RequestClientCredentialsTokenAsync(new
ClientCredentialsTokenRequest
{
Address = discoveryDocument.TokenEndpoint,
ClientId = "client_id",
ClientSecret = "client_secret",
Scope = "ApiOne",
});
The code to validate the Token is here:
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", config =>
{
config.Authority = "https://localhost:44335/";
config.Audience = "ApiOne";
config.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateActor = true,
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true
};
config.RequireHttpsMetadata = false;
});
I believe the JWT token should contain the audience parameter. When I request the JWT I can't find a way to set the audience parameter.
I've used jwt.io to debug my JWT token and this confirms the audience value is not set. I expected setting the Scope on the request would do this.
What is lacking is the ApiScope and ApiResource configuration in IdentityServer.
First you need an ApiScope defined, like:
new ApiScope(name: "ApiOneScope",
displayName:"You can manage the ApiOne system.",
userClaims: new List<string>{ });
The ApiScope is a scope that the client can request access to.
then you need a ApiResource defined like:
new ApiResource()
{
Name = "ApiOne",
DisplayName = "Orders API Service",
Scopes = new List<string> { "ApiOneScope" },
};
The ApiResource is the actual Api, that end up in the audience claim when the clients requests the scope named ApiOneScope.
To complement this answer, I write a blog post that goes into more detail about this topic:
IdentityServer – IdentityResource vs. ApiResource vs. ApiScope

The audience is invalid error

I have 3 projects 1- Javascript SPA 2- Web API Project, 3- IdentityServer with EF Core
I started debugging API and Identity Server and successfully get the jwt token but, when I try to get value from API method which has Authorize Attribute I get an error:
WWW-Authenticate →Bearer error="invalid_token", error_description="The audience is invalid"
I could not found any property about audience in auth options. This is my configuration in API project
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
ApiSecret="secret",
Authority = "http://localhost:5000",
ApiName="fso.Api",
RequireHttpsMetadata = false,
});
And my Config.cs file in Identity
public class Config
{
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource()
{
Name = "fso.Api",
DisplayName = "feasion API",
Scopes =
{
new Scope("api1"),
new Scope(StandardScopes.OfflineAccess)
},
UserClaims =
{
JwtClaimTypes.Subject,
JwtClaimTypes.EmailVerified,
JwtClaimTypes.Email,
JwtClaimTypes.Name,
JwtClaimTypes.FamilyName,
JwtClaimTypes.PhoneNumber,
JwtClaimTypes.PhoneNumberVerified,
JwtClaimTypes.PreferredUserName,
JwtClaimTypes.Profile,
JwtClaimTypes.Picture,
JwtClaimTypes.Locale,
JwtClaimTypes.IdentityProvider,
JwtClaimTypes.BirthDate,
JwtClaimTypes.AuthenticationTime
}
}
};
}
public static List<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Email(),
new IdentityResources.Profile(),
};
}
// client want to access resources (aka scopes)
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "fso.api",
AllowOfflineAccess=true,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AllowedScopes =
{
StandardScopes.OfflineAccess,
"api1"
}
}
};
}
}
See here for what this claim is about:
The aud (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the aud claim when this claim is present, then the JWT MUST be rejected....
So your API's name must exist in the aud claim for the JWT to be valid when it is validated by the middleware in your API. You can use jwt.io to look at your token by the way, that can be useful to help make sense of it.
In order to have IdentityServer to add your API's name to the aud claim your client code (which is attempting to get a resource from the API and therefore needs an access token) should request a scope from your API. For example like this (from an MVC client):
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
Authority = Configuration["IdpAuthorityAddress"],
ClientId = "my_web_ui_id",
Scope = { "api1" },
//other properties removed...
});
To avoid the error, audience should be consistently added in 4 places
In My (e.g. MVC) client as custom Scope.
In API application as ApiName
In IdentityServer Clients configuration as AllowedScope
In API Resources configuration as ApiResource
See details ( previously available in IdentityServer4 wiki):
When configuring a new API connection in identityServer4, you can get an error:
WWW-Authenticate: Bearer error="invalid_token",
error_description="The audience is invalid"
To avoid the error, Audience should be consistently added in 4 places
In My (e.g. MVC) client as custom Scope :
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
Authority = Configuration["IdpAuthorityAddress"],
ClientId = "my_web_ui_id",
Scope = { "openid", "profile", "offline_access", "MyApi" },
//other properties removed for brevity...
});
In API application as ApiName
//Microsoft.AspNetCore.Builder.IdentityServerAuthenticationOptions
var identityServerAuthenticationOptions = new IdentityServerAuthenticationOptions()
{
Authority = Configuration["Authentication:IdentityServer:Authority"],
RequireHttpsMetadata = false,
EnableCaching = false,
ApiName = "MyApi",
ApiSecret = "MyApiSecret"
};
In IdentityServer \IdentityServerHost\Configuration\Clients.cs
(or corresponding Clients entry in the database)
var client = new Client
{
ClientId = clientId,
//other properties removed for brevity...
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
//IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.OfflineAccess, "MyApi",
},
};
In IdentityServer \IdentityServerHost\Configuration\Resources.cs (or corresponding ApiResource entry in the database) as apiResource.Scopes
var apiResource = new ApiResource
{
Name = "MyApi",
ApiSecrets =
{
new Secret("MyApiSecret".Sha256())
},
UserClaims =
{
JwtClaimTypes.Name,
JwtClaimTypes.Profile,
},
};
In your app configuration file in AD configuration section add "Audience" line:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "<-- Enter the Client Id -->",
"Audience": "<-- Enter the Client Id -->",
"TenantId": "<-- Enter the tenantId here -->"
}
In my case "ClientId" & "Audience" was the same.
P.S.: And if after that you'll see
IDW10201: Neither scope or roles claim was found in the bearer token
Add another line to AD configuration:
"AllowWebApiToBeAuthorizedByACL": true
More here
In IdentityServer had to add claim "aud" to the jwt Token. In Order to do that under .AddJwtBearer("Bearer", options => options.Audience="invoice" and set ApiResource
Reference Link https://identityserver4.readthedocs.io/en/latest/topics/resources.html#refresources
public static readonly IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("invoice", "Invoice API")
{
Scopes = { "invoice.read", "invoice.pay", "manage" }
}
};
}