I would like to use a .Net Core 3.1 web app to allow an app (e.g. iPhone or Web Javascript) to authenticate with a username and password and receive a Jason Web Token (JWT) that contains claims about the user... if it succeeds, then the JWT can be sent as a bearer token to an API that would decode and validate the JWT (the token would be asymmetric and use a public/private key pair) and retrieve any claims that are embedded... perhaps a bonus if the app could decode the JWT as well in order to retrieve any claims.
Any thoughts on if this approach is possible? And, if there are any discussions or examples of how this might be done, that would be terrific.
Take a look at the examples that IdentityServer4 is providing. This sample/quickstart includes the case you are describing. https://github.com/IdentityServer/IdentityServer4/tree/main/samples/Quickstarts/6_AspNetIdentity/src
The API needs to be a scope in the IdentityServer4 configuration. It has a connection with the authority (IdentityServer4):
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:5001";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
The Client, in this example an MVC Client, needs to be a client in IdentityServer4. There are many types of GrantTypes. https://identityserver4.readthedocs.io/en/latest/topics/grant_types.html
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.Scope.Add("api1");
options.SaveTokens = true;
});
Hope this helps you out
Related
We are using ASP.NET Zero based on ASP.NET Boilerplate (.NET Core). We have a Mobile App that authenticates with AWS Cognito and needs to consume some API's and Application Services on our application. (We already make sure that each user in Cognito also has a corresponding user in AbpUsers with permissions.)
The Mobile App will authenticate with a JWT Bearer Token from Cognito. We have added JWT Validation to our Startup.cs as follows:
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => {
options.ClaimsIssuer = CognitoIssuer;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
var json = new WebClient().DownloadString(CognitoIssuer + "/.well-known/jwks.json");
var keys = JsonConvert.DeserializeObject<JsonWebKeySet>(json)!.Keys;
return keys;
},
RoleClaimType = "cognito:groups",
NameClaimType = "username",
ValidIssuer = CognitoIssuer,
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidateAudience = false
};
});
This validation works in so far as when I decorate an API with the [Authorize] attribute, the API only executes if a valid JWT token is provided. (With an invalid token, a 401 error is returned as expected.)
The problem we have is that the user identity is not being set. I can see that the Claims Principal contains the claims from Cognito. But inside the API the current user and tenant appears as NULL. I assume that after JWT validation I'm missing steps to "map" the claims/user from Cognito to an actual user that exists in the ABP database so that the AbpSession can reflect this user. But how do I do that? How do I set the Identity/User/Tenant/AbpSession so that it can correctly and seamlessly be picked up in my Application Services and Controllers after I've validated the JWT token? (So that Application Services and Controllers can work just as seamlessly as if the user had actually logged on in the ABP app?)
I am new JWT in DOTNet core web Api. In our application, we are getting access_token from the Microsoft site. https://login.microsoftonline.com/
We would like to validate the token (RS256 algo) in the .net core (Api) but we don't have the PUBLIC KEY.
Note: I already have a token. How can we validate JWT with our public key and any other thing? I only have an access token.
With JWT you basically have two scenarios:
you created your JWT yourself and you know the keys used for it. Than you can write the validation, or pass the parameters to .net core pipeline.
you got the JWT from external authority. In this case the authority (in your particular case - Microsoft) knows how to validate the JWT.
Authority will implement the JWT protocol and expose it via a URL. Normally you need to know two things: authority and audience (recipient of the token).
Now good news is that .net core handles the protocol details for you, all you need to do is to set up the authentication pipeline. This is what it boils down to:
services.AddAuthentication()
.AddJwtBearer("schemeName", options =>
{
options.Audience = "your audience";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "your domain",
ValidateAudience = true,
ValidAudience = "your audience",
ValidateIssuerSigningKey = true,
IssuerSigningKeys = jwks, // use "Keys" as JsonWebKeySet or "Key" (below), just one of them
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)), // your encoding etc may differ
RequireSignedTokens = true,
RequireExpirationTime = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
ValidAlgorithms = new[] { SecurityAlgorithms.EcdsaSha256, }, // your algorithm may differ
};
})
For details, do some reading on JWT authentication in .net core, e.g. JWT Validation and Authorization in ASP.NET Core. There are a lot of articles on the topic.
I am using asp.net core 5.0 and openidconnect to authenticate users. My application will be used by several organizations.
My database stores the openid Connect options (client id, client secret, authority, etc) for each organization. I authenticated users by getting all the stored openid connect options (for all organizations) in my database and add each as below in the startup.cs
foreach(OrganizationSetting setting in settings)
{
authBuilder.AddOpenIdConnect(settings.AuthenticationScheme, options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = setting.Authority
options.ClientId = setting.ClientId;
options.CallbackPath =setting.CallbackPath;
options.ClientSecret =setting.ClientSecret;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.Scope.Add("openid");
options.SaveTokens = true;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = false,
SaveSigninToken = true
};
})
}
As you see, I must have different values for the autheticationscheme property and for the options.CallbackPath, else the application will throw an exception.
Since I am new to this, is there a better way to achieve my goal? maybe setting the clientid/tenantid at runtime before calling the challenge method ?
Thank you
I' developed a simple app to pratice.
The combination is ASP.NET CORE with JWT Token.
On my asp.net core 3.1 server, I have this DI code:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = configuration["jwt:Issuer"],
ValidAudience = configuration["jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["jwt:Key"]))
};
});
I can easily connect with Postman or my BlazorApp to generate a token and consume other endpoints with the token.
On Blazor, I just need to insert a DefaultHeader:
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
Now, I'm trying to do the same with a Xamarin Forms app.
I tried to use the generated token on the default header, but I got an Unauthorized response.
To debug the Xamarin Forms app I had to change my localhost endpoint names to 127.0.0.1 (so the emulator can reach the asp.net API on my machine).
I tried to change the issuer of my JWT token to 127.0.0.1 instead localhost but no success.
Can someone help?
Thanks in advance.
I got it after 2 days.
My asp.net core api was using HttpsRedirect and my Xamarin Mobile wasn't pointing to HTTPS port.
I don't know why, because postman and blazor works with both endpoints (HTTP and https ports) but the Xamarin started working since I used the https endpoint.
Now it's fine.
Thanks anyway
I am using Identity Server 4 to provide access to multiple applications, some of which are Asp.Net Core, some are classic Asp.Net (MVC & WebForms).
The applications need to share their cookie due to the design of the old WebForms application (basically the API is 'in' the Web App, so it is protected by the Web App's cookie based authentiation). I'm using Data Protection to achieve the cookie sharing but this creates a separate issue.
If I login via the Core application and then navigate to the WebForms application, I don't know how to get the access_token to make calls to other APIs.
In the Core application I can retreive it from the HttpContext:
_httpContextAccessor.HttpContext.GetTokenAsync(AuthConstants.AccessToken).Result
What is the equivalent method to get the access_token in WebForms?
For context this is my configuration for the Core application authentication:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services
.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Cookie = new CookieBuilder
{
Domain = configuration.CookieDomain,
HttpOnly = configuration.CookieHttpOnly,
Name = configuration.CookieName
};
options.ExpireTimeSpan = configuration.AuthTimeout;
options.SlidingExpiration = configuration.AllowSlidingAuthTimeout;
options.DataProtectionProvider = DataProtectionProvider.Create
(
new DirectoryInfo(configuration.DataProtectionKeyDirectory)
);
})
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Authority = configuration.AuthorizationServerUri;
options.RequireHttpsMetadata = configuration.RequireHttps;
options.ClientId = configuration.Client;
options.ClientSecret = configuration.Secret;
options.ResponseType = configuration.ResponseType;
options.Scope.Clear();
foreach (var resource in configuration.Scope.Split(" "))
options.Scope.Add(resource);
options.SignedOutRedirectUri = configuration.RedirectUri;
options.UseTokenLifetime = configuration.UseAuthServerLifetime;
options.SaveTokens = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role
};
});
Just to close this out I did not find the answer I was looking for and ended up separating the API into it's own service as should have been done originally. That nullified this issue.