I am trying to use a web api. They require that I use Jwt tokens. I am trying to do this in a asp.net 4.5.2 web app on azure. I have downloaded the Microsoft.IdentityModel.Tokens Nuget package and would like to use it to generate the needed tokens.
The web api I am trying to use has an example in node.js on how to generate the proper token. Here is their example:
var jwt = require('jsonwebtoken');
var payload = {
iss: api_key,
exp: ((new Date()).getTime() + 5000)
};
//Automatically creates header, and returns JWT
var token = jwt.sign(payload, api_secret);
All of the examples for the nuget package do a ton more than what is shown in the node.js example. Things like creating a secret value, claims identity, signing credentials and all kinds of other stuff.
I have the api_key value and the api_secret value. Can someone please tell me how to do the same thing using the nuget package as what they show using node.js?
After doing some research I think I have a solution. This is the simplest it gets in c# unfortunately, but I think its a passable solution considering we are talking apples and oranges. It seems C# is twice as verbose about the options and doesn't have sensible defaults for anything.
using System;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
namespace stackoverflow
{
class Program
{
static void Main(string[] args)
{
String api_key = "apiKey123123";
String api_secret = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1";
var signingKey = Convert.FromBase64String(api_secret);
JwtHeader jwtHeader = new JwtHeader(
new SigningCredentials(
new SymmetricSecurityKey(signingKey),
SecurityAlgorithms.HmacSha256Signature
)
);
JwtPayload jwtPayload = new JwtPayload {
{"iss", api_key},
{"exp", ((DateTimeOffset)DateTime.UtcNow).AddMilliseconds(5000).ToUnixTimeMilliseconds() }
};
var jwt = new JwtSecurityToken(jwtHeader, jwtPayload);
var jwtHandler = new JwtSecurityTokenHandler();
Console.Write(jwtHandler.WriteToken(jwt));
Console.ReadLine();
}
}
}
Only import was this package:
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.2.1" />
Related
I want to implement OAuth2.0 token in ASP.NET Core Web API. I have seen many tutorials and videos but all are doing the traditional way or in ASP.NET only not in Core. So I want the way to implement in visual studio 2022 with latest version of ASP.NET Core. Please help
I have seen many tutorials and videos but all are doing the traditional way or in ASP.NET only not in Core. So I want the way to implement in visual studio 2022 with latest version of ASP.NET Core. Please help
You can use Jwt authentication to protect your web api and this is one of the method based on OAuth2.0. Here's a blog and the following codes are based on it.
OAuth2.0 is a protocol but not the implement. So you can't find samples for it. But when you searched Jwt auth, Azure AD into .net 6 or some other products, you will find many doucuments.
Let's see some additional information which may also help you:
Let's go back to the sample, in this scenario, you have to integrate the authentication first. In .net 6, going to program.cs and adding these code:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
//adding jwt auth
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
//define which claim requires to check
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
//store the value in appsettings.json
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});
...
app.UseRouting();
//adding UseAuthentication
app.UseAuthentication();
app.UseAuthorization();
In appsettings.json:
"Jwt": {
"Key": "ThisismySecretKey",
"Issuer": "Test.com"
}
Then, pls add [Authorize] before the api controller, then you've established the authentication and when accessing the api without the correct jwt token, you will get 401 error:
Let's generate an access token then test calling the api with the token. In another Controller without [Authorize], adding code like this:
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
private IConfiguration _config;
public HomeController(IConfiguration config)
{
_config = config;
}
public IActionResult Index()
{
ViewBag.accessToken = generateJwt();
return View();
}
private string generateJwt() {
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
//If you've had the login module, you can also use the real user information here
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, "user_name"),
new Claim(JwtRegisteredClaimNames.Email, "user_email"),
new Claim("DateOfJoing", "2022-09-12"),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken(_config["Jwt:Issuer"],
_config["Jwt:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(120),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
Then calling the api with the token, you can decode the token first:
I have actually an asp.net website application, that can deliver token to an user with the following way :
the user logs into the application, go to a specific page and obtains a clientid and a clientsecret.
then, he calls the following api "....api/token" by giving clientid and clientsecret (client credentials grant type) to get the token.
This is the associated code :
using Microsoft.Owin;
using Owin;
using System;
using Microsoft.Owin.Security.OAuth;
[assembly: OwinStartup(typeof(MyApp.Web.App_Start.OwinStartup))]
namespace MyApp.Web.App_Start
{
public class OwinStartup
{
public void Configuration(IAppBuilder app)
{
OwinWebApiStartup.Configuration(app);
}
}
}
public static class OwinWebApiStartup
{
public static void Configuration(IAppBuilder app)
{
var provider = //my provider implementation;
var oauthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/api/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(20),
Provider = provider,
};
app.UseOAuthAuthorizationServer(oauthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
AccessTokenProvider = //my provider implementation,
});
}
}
This is working nicely. But I would like to add a new feature, where a javascript client code, not an user anymore, would like to call my apis, and so it will need to have a token, but do not have a clientid and clientsecret.
This is my idea :
Create a new api endpoint, (only this one will be reachable by my javascript client code without token, and there, the code will generate a token (thanks to the username of the current user connected) and return this one (that will
be the same that an user could have obtained with the existing method) to be used by the javascript client code
I faced this problem in the past. I solved this via querystring, cause owin could only provide one token ressource. In fact it makes sense to rely on owin and on not implementing your own code.
My pseudo solution:
KeyValuePair<string, string[]> typePair = ctx.Request.Query.FirstOrDefault(x => x.Key == "type");
LoginType? loginType = GetLoginType(typePair);
[...]
switch (loginType)
{
case LoginType.User:
[...]
////within this routine you could set your claims depending on your needs
If you get another solution, I'd be grateful for sharing
Is there a package for authenticating with Azure AD, for Asp.net Core?
For example, the following Authentication packages exist, when querying Nuget:
Microsoft.AspNetCore.Authentication.Facebook
Microsoft.AspNetCore.Authentication.Twitter
Microsoft.AspNetCore.Authentication.Google
Microsoft.AspNetCore.Authentication.MicrosoftAccount
I tried to use the MicrosoftAccount package, but after wiring it up successfully, I got the following error from the Microsoft page:
Application '{my-app-id}' {my-app-name} is not supported over the /common or /consumers endpoints. Please use the /organizations or tenant-specific endpoint.
There's examples that don't use a middleware package, but the middleware package offers ease of use, and more directly integrates with the Identity framework.
Are there any direct packages that use Azure AD, or anyway to specify that the MicrosoftAccount package should point to the organizations/azure/tenant url?
This is what worked for me:
authBuilder
.AddMicrosoftAccount(Auth.Constants.AuthenticationScheme, options =>
{
options.ClientId = clientId;
options.ClientId = clientSecret;
if (tenantId != null)
{
var resource = "https://graph.microsoft.com";
options.AuthorizationEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/authorize?resource={resource}";
options.TokenEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/token?resource={resource}";
}
});
tenantId - look at the "Endpoints" section from the "App registrations" blade. You can extract it from the URLs, or use those URLs directly instead of what I've done above.
clientId - this is your "Application ID" in your registered app.
clientSecret - this is a password you create and register under the "Keys" section of your registered app.
You can then get back more information by using the access token with https://graph.microsoft.com, such as adding options like the following:
options.ClaimActions.DeleteClaim(ClaimTypes.Name);
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "displayName");
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "userPrincipalName");
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
// Get the GitHub user
var request = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me/");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var user = JObject.Parse(contents);
context.RunClaimActions(user);
}
};
I've been attempting to implement a working solution into our .Net 4.5.2 web site project. My goal is to convert my web site project, which is a web forms application (not MVC), into using identity server 3 (possibly via thinktecture's implementation) to authenticate/authorize users.
Is this possible? How will the site life cycle change or can it say the same with a session object? Can I use identity server to gain the access token and then setup a session, and only request a new access token when the session expires or the access token expires?
Some Back Story:
I do have a working identity server 3 setup at the following location https://localhost:44399. Which responds with all the appropriate calls and limitations when I use a straight html file, but when I tried to implement the same thing in the login page (portal.aspx) it seems to forget the user and does not behave the same. The website (the client) is setup on https://localhost:9898. And the login page is https://localhost:9898/portal.aspx.
I'm using the oidc-client.js on the portal.aspx page, but I keep getting CORS (cross-browser origin scripting) errors when it is trying to authenticate. Is this an issue with web forms, this does not happen when using a regular html page?
This is a .net 4.5.2 "web site project" (not a "web application project") which uses web forms, not MVC. I have got many demo MVC applications to work correctly, but when I try to take the knowledge I have to the web forms application, I see that I really don't have a grasp on what is going on.
I have added the following nuget packages:
Microsoft.Bcl v1.1.10
Microsoft.Owin v3.0.1
Microsoft.Bcl.Async v1.0.168
System.IdentityModel.Tokens.Jwt v5.1.0
Newtonsoft.Json v9.0.1
Owin v1.0.0
Microsoft.IdentityModel.Logging v1.1.0
Microsoft.Owin.Host.SystemWeb v3.0.1
System.IdentityModel.Tokens.Jwt v5.1.0
Microsoft.IdentityModel.Tokens v5.1.0
Microsoft.Bcl.Build v1.0.21
Thinktecture.IdentityModel v3.6.1
When I attempt to add the startup.cs class it tells me that these classes are typically added to the App_Code directory, and do I want to put it there? I have tried both the root and the App_Code directory and neither seem to register when they are being called. I have added the package, thinking that this is what does the call:
Microsoft.Owin.Host.SystemWeb
But it seems like the startup class is not registered. So how do I get this to register in the pipeline so that 401 not authorized errors will send the user to the identity server?
Here is my startup class:
using ExpenseTracker.WebClient.Helpers;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Newtonsoft.Json.Linq;
using Owin;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Web;
using System.Web.Helpers;
using Thinktecture.IdentityModel.Client;
[assembly: OwinStartup(typeof(ExpenseTracker.WebClient.Startup))]
namespace ExpenseTracker.WebClient
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
AntiForgeryConfig.UniqueClaimTypeIdentifier = "unique_user_key";
app.UseResourceAuthorization(new AuthorizationManager());
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "mvc",
Authority = ExpenseTrackerConstants.IdSrv,
RedirectUri = ExpenseTrackerConstants.ExpenseTrackerClient,
SignInAsAuthenticationType = "Cookies",
ResponseType = "code id_token token",
Scope = "openid profile roles expensetrackerapi offline_access",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
MessageReceived = async n =>
{
EndpointAndTokenHelper.DecodeAndWrite(n.ProtocolMessage.IdToken);
EndpointAndTokenHelper.DecodeAndWrite(n.ProtocolMessage.AccessToken);
//var userInfo = await EndpointAndTokenHelper.CallUserInfoEndpoint(n.ProtocolMessage.AccessToken);
},
SecurityTokenValidated = async n =>
{
var userInfo = await EndpointAndTokenHelper.CallUserInfoEndpoint(n.ProtocolMessage.AccessToken);
// use the authorization code to get a refresh token
var tokenEndpointClient = new OAuth2Client(
new Uri(ExpenseTrackerConstants.IdSrvToken),
"mvc", "secret");
var tokenResponse = await tokenEndpointClient.RequestAuthorizationCodeAsync(
n.ProtocolMessage.Code, ExpenseTrackerConstants.ExpenseTrackerClient);
var givenNameClaim = new Claim(
Thinktecture.IdentityModel.Client.JwtClaimTypes.GivenName,
userInfo.Value<string>("given_name"));
var familyNameClaim = new Claim(
Thinktecture.IdentityModel.Client.JwtClaimTypes.FamilyName,
userInfo.Value<string>("family_name"));
var roles = userInfo.Value<JArray>("role").ToList();
var newIdentity = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType,
Thinktecture.IdentityModel.Client.JwtClaimTypes.GivenName,
Thinktecture.IdentityModel.Client.JwtClaimTypes.Role);
newIdentity.AddClaim(givenNameClaim);
newIdentity.AddClaim(familyNameClaim);
foreach (var role in roles)
{
newIdentity.AddClaim(new Claim(
Thinktecture.IdentityModel.Client.JwtClaimTypes.Role,
role.ToString()));
}
var issuerClaim = n.AuthenticationTicket.Identity
.FindFirst(Thinktecture.IdentityModel.Client.JwtClaimTypes.Issuer);
var subjectClaim = n.AuthenticationTicket.Identity
.FindFirst(Thinktecture.IdentityModel.Client.JwtClaimTypes.Subject);
newIdentity.AddClaim(new Claim("unique_user_key",
issuerClaim.Value + "_" + subjectClaim.Value));
newIdentity.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
newIdentity.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
newIdentity.AddClaim(new Claim("expires_at",
DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
n.AuthenticationTicket = new AuthenticationTicket(
newIdentity,
n.AuthenticationTicket.Properties);
},
}
});
}
}
}
And to make things worse, adding Microsoft.Owin.Host.SystemWeb does not compile due to strange errors like:
The type or namespace name 'SessionState' does not exist in the
namespace 'System.Web' (are you missing an assembly reference?)
So I'm trying to sort out web-based authentication using the WSFederation protocal. We've sorted out the setup, and my web app can reach the login page, after some headache:
(Asp.net on-premises authentication - The remote certificate is invalid according to the validation procedure)
Now I'm getting a 'IDX10201: None of the the SecurityTokenHandlers could read the 'securityToken' error. From what I understand, we'll need middleware to deal with the security tokens. So I'm trying to get started with this:
https://www.scottbrady91.com/Katana/WS-Federation-Token-Encryption-using-Microsoft-Katana
So I've set the TokenValidationParameters option in the WsFederationAuthenticationOptions, but I'm getting an error from VisualStudio saying that 'Cert' does not exist in the current context. I'm confused as to why, as my code is nearly identical to the guides.
I'm also wondering if our certificate has simply been improperly configured. I came across some SSL guidelines for ADFS, and I know our IT guy hasn't gone down that road (yet). I'd like to rule that out as a possible cause, but if someone knows that it is, or is not, the cause, it'd save me time and be greatly appreciated.
EDIT: After some reading, there are some things that are unclear to me? We're using an ADFS server to handle the credentials, but as I understand it, ADFS should also handle our tokens without any additional work. Am I wrong? Should I be using middleware? Or is there a problem with the ADFS server configuration?
using System;
using System.Collections.Generic;
using System.Configuration;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.WsFederation;
using Owin;
using System.Security.Cryptography.X509Certificates;
using System.Security;
using System.Net.Security;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.IdentityModel.Tokens;
using Microsoft.Owin;
using RCHHRATool;
using System.Net;
using System.IdentityModel.Selectors;
namespace RCHHRATool
{
public partial class Startup
{
private static string realm = ConfigurationManager.AppSettings["ida:Wtrealm"];
private static string adfsMetadata = ConfigurationManager.AppSettings["ida:ADFSMetadata"];
private X509Certificate2 certificate;
public void ConfigureAuth(IAppBuilder app)
{
Debug.WriteLine("Configure Auth Started");
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType =
WsFederationAuthenticationDefaults.AuthenticationType });
//System.Net.ServicePointManager.ServerCertificateValidationCallback.
//ServicePointManager.ServerCertificateValidationCallback = RemoteCertificateValidationCB;
var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach(X509Certificate2 cert in store.Certificates)
{
Debug.WriteLine(cert.Issuer);
if (cert.Issuer.Equals("CN=xxxxx.xxxxx.com"))
{
this.certificate = new X509Certificate2(cert);
}
}
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
SignInAsAuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
Wtrealm = "https://localhost:44340",
Wreply = "http://localhost:56879",
MetadataAddress = adfsMetadata,
AuthenticationType = "adfs",
SecurityTokenHandlers = new SecurityTokenHandlerCollection
{
new X509SecurityTokenHandler
{
Configuration = new SecurityTokenHandlerConfiguration
{
IssuerTokenResolver = new X509CertificateStoreTokenResolver(StoreName.Root,
StoreLocation.LocalMachine)
}
}
}
//},
//TokenValidationParameters = new TokenValidationParameters
//{
// ValidAudience = "https://localhost:44340/",
// ValidIssuer = "xxxxx.xxxxx.com",
// IssuerSigningToken = new X509SecurityToken(this.certificate)
//}
});
}
Turns out that asp.net (framework 4.6.1) & ws-federation doesn't handle encrypted security tokens out of the box. I followed a great guide to resolve the token error. After some tuning (watch your certificate footprint, and make sure your certificates are in trusted root), I managed to get the authentication working.