I have a signalR server and I need to validate an OAuth Token that the client will get from Azure AD. I want to do it in the AuthorizeHubConnection method.
I tried this http://geekswithblogs.net/shaunxu/archive/2014/05/27.aspx which basically does this:
var d
dataProtectionProvider = new DpapiDataProtectionProvider();
var secureDataFormat = new TicketDataFormat(dataProtectionProvider.Create());
// authenticate by using bearer token in query string
var token = request.QueryString.Get(WebApiConfig.AuthenticationType);
var ticket = secureDataFormat.Unprotect(token);
This will always return null in the ticket.
After a bit of searching I came across this article: http://ronaldwildenberg.com/signalr-hub-authentication-with-adal-js-part-2/
Here is what it does:
public class JwtTokenAuthorizeAttribute : AuthorizeAttribute
{
// Location of the federation metadata document for our tenant.
private const string SecurityTokenServiceAddressFormat =
"https://login.windows.net/{0}/federationmetadata/2007-06/federationmetadata.xml";
private static readonly string Tenant = "yourtenant.onmicrosoft.com";
private static readonly string ClientId = "12345678-ABCD-EFAB-1234-ABCDEF123456";
private static readonly string MetadataEndpoint = string.Format(
CultureInfo.InvariantCulture, SecurityTokenServiceAddressFormat, Tenant);
private static readonly IIssuerSecurityTokenProvider CachingSecurityTokenProvider =
new WsFedCachingSecurityTokenProvider(
metadataEndpoint: MetadataEndpoint,
backchannelCertificateValidator: null,
backchannelTimeout: TimeSpan.FromMinutes(1),
backchannelHttpHandler: null);
public override bool AuthorizeHubConnection(
HubDescriptor hubDescriptor, IRequest request)
{
// Extract JWT token from query string (which we already did).
...
// Validate JWT token.
var tokenValidationParameters =
new TokenValidationParameters { ValidAudience = ClientId };
var jwtFormat =
new JwtFormat(tokenValidationParameters, CachingSecurityTokenProvider);
var authenticationTicket = jwtFormat.Unprotect(userJwtToken);
...
The problem with this is that it proposes to copy classes from the Katana project: https://katanaproject.codeplex.com/SourceControl/latest#src/Microsoft.Owin.Security.ActiveDirectory/WsFedCachingSecurityTokenProvider.cs.
This looks super ugly. Another problem is that I don't know the tenant id and I couldn't find it anywhere with the token. So even if this works, I would be one step away.
To wrap it up: I want to find a way to validate the AzureAD token with SignalR. It looked like a simple thing in the beginning. Is there a simpe way for this?
It was simple:
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
tokenHandler.ValidateToken(token, authTokenValidationParameters, out validatedToken);
Related
I'm using the Microsoft.AspNetCore.Authentication.JwtBearer and System.IdentityModel.Tokens.Jwt packages for my .NET Core project.
When configuring the services I'm adding logic to the OnTokenValidated event.
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(jwtBearerOptions =>
{
// ... set TokenValidationParameters ...
jwtBearerOptions.Events = new JwtBearerEvents()
{
OnTokenValidated = (tokenValidatedContext) =>
{
JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
string tokenWithoutSignature = jwtSecurityTokenHandler.WriteToken(tokenValidatedContext.SecurityToken);
// ... full token from request? ...
}
};
});
Since I know the context only returns me the token without the signature I would like to know how I can
either get the full token with signature
or the signature additionally to add it to the tokenWithoutSignature string
If this is not possible:
I'm generating new tokens this way
public string GenerateAccessToken(IDictionary<string, object> payload)
{
SymmetricSecurityKey symmetricSecurityKey = new SymmetricSecurityKey(Convert.FromBase64String("secret from config"));
SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
{
Claims = payload,
Expires = DateTime.Now, // value from config
SigningCredentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256Signature)
};
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken = tokenHandler.CreateToken(tokenDescriptor);
string token = tokenHandler.WriteToken(securityToken);
return token;
}
Maybe I can either retrieve
the token string without the signature
only the signature
within this method?
If nothing works:
Since I kow a bearer token always contains three parts like
header.payload.signature
I could split the string segments to an array, take the first and second element from the array and create a new string of
firstString + . + secondString
That should give me the token without the signature. Are there any better ideas to cut off the signature from a full token?
Why do I want to achieve this?
This question is based on this one
Security token from TokenValidatedContext from the OnTokenValidated event listener is missing last string segment
I'm working with access and refresh tokens. During validation, I have to compare the token from the request with the token from the database. The token in the database contains the signature too. So I'm facing the same problem as linked above.
That's why I thought about multiple solutions and wrote them down here. If the TokenValidatedContext is not able to return me the signature it seems I have to store the JWT to the database without the signature. And also for this case, I need to separate the signature from the generated JWT.
Without using refresh tokens I only store the maximum session lifetime of a user to the database. The flow is based on this idea
Only store the time of the JWT with the highest lifetime to the database instead of the whole JWT
With using refresh tokens I came up with the following flow. Since you know that the OnTokenValidated handles the validation logic the following logic is additional. I have a database table with
username | access_token | refresh_token | refresh_token_expires_at
and the primary key is a composite key of username + access_token. Refresh tokens are just some random strings generated like so
public string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
and that's why I'm storing an additional expiration date to it. It should be able to expire after some time.
Signing in
Store the generated access and refresh token and its expiration time for a user to the database. Either store the full access token or the access token without signature to the database (depends on the solution of this question).
Hitting a protected endpoint
Check if that access token for that user exists in the database.
Hitting the refresh endpoint
Check if the database refresh token has expired. If not, compare this one with the refresh token from the request. If everything is fine, remove the old access and refresh token from the database and store the new generated access and refresh token to the database.
Signing out
Remove that access and its connected refresh token from the database.
I don't quite understand why you do all this, but if all you need is the original token, you can use one of these:
o.Events = new JwtBearerEvents
{
OnTokenValidated = (context) =>
{
var securityToken = (System.IdentityModel.Tokens.Jwt.JwtSecurityToken)context.SecurityToken;
var token = securityToken.RawData; // "ey...."
var tokenHeader = securityToken.RawHeader; // "ey...."
var tokenPayload = securityToken.RawPayload; // "ey...."
var tokenSignatur = securityToken.RawSignature; // "ey...."
var fullBearerHeader = context.Request.Headers["Authorization"]; // "Bearer ey..."
return Task.CompletedTask;
}
};
You probably want to make the code a bit more safe with regards to type casting etc., but it should give you the token.
Why do you want to manipulate the token? If it is just to Validate the token you can use below code.
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
//ValidIssuer = Configuration["Issuer"],
//ValidAudience = Configuration["Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Key"]))
};
});
and
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
app.UseMvc();
}
I have a customer that is trying to access their calendars from our web application. Everything works for all of our other customers, so I am not sure what is different here except this customer is in Australia and using a non gmail.com email address.
The customer is able to authorize our application and we do get a oauth token for the user. We request calendar access and the customer granted it. When we request a list of all of the calendars, we get the invalid grant message.
Below is the code that we use to access their calendars. The method being called is GetAllWritableCalendars.
public class GoogleCalendarAdapter : ICalendarAdapter {
#region attributes
private readonly ISiteAuthTokenQueryRepository _tokenRepo;
private readonly GoogleCalendarSettings _settings;
private const string APPNAME = "SomeAppName";
private const string ACL_OWNER = "owner";
private const string ACL_WRITER = "writer";
#endregion
#region ctor
public GoogleCalendarAdapter(ISiteAuthTokenQueryRepository tokenRepo,
GoogleCalendarSettings settings) {
_tokenRepo = tokenRepo;
_settings = settings;
}
#endregion
#region methods
private GoogleAuthorizationCodeFlow BuildAuthorizationCodeFlow() {
return new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer() {
ClientSecrets = BuildClientSecrets(),
Scopes = BuildScopeList()
});
}
private CalendarService BuildCalendarService(SiteAuthToken token) {
return new CalendarService(new BaseClientService.Initializer() {
ApplicationName = APPNAME,
HttpClientInitializer = BuildUserCredential(token)
});
}
private ClientSecrets BuildClientSecrets() {
return new ClientSecrets() {
ClientId = _settings.ClientId,
ClientSecret = _settings.ClientSecret
};
}
private string[] BuildScopeList() {
return new [] { CalendarService.Scope.Calendar };
}
private UserCredential BuildUserCredential(SiteAuthToken token) {
TokenResponse responseToken = new TokenResponse() {
AccessToken = token.AccessToken,
RefreshToken = token.RefreshToken
};
return new UserCredential(BuildAuthorizationCodeFlow(), APPNAME, responseToken);
}
public async Task<List<Cal>> GetAllWritableCalendars(Guid siteGuid) {
SiteAuthToken token = await GetToken(siteGuid);
CalendarService svc = BuildCalendarService(token);
IList<CalendarListEntry> calendars = svc.CalendarList
.List()
.Execute()
.Items;
return calendars.Where(c => c.AccessRole.Equals(ACL_OWNER, StringComparison.CurrentCultureIgnoreCase) ||
c.AccessRole.Equals(ACL_WRITER, StringComparison.CurrentCultureIgnoreCase))
.Select(c => new Cal() {
Id = c.Id,
Name = c.Summary
})
.OrderBy(o => o.Name)
.ToList();
}
private async Task<SiteAuthToken> GetToken(Guid siteGuid) {
SiteAuthToken retVal = await _tokenRepo.GetSiteAuthToken(siteGuid);
if (retVal == null) {
throw new ApplicationException($"Could not find a SiteAuthToken for specified site (SiteGuid: {siteGuid})");
}
return retVal;
}
#endregion
The credentials are the authorization from Google to Your Application to use the scopes you have set-up, this is okay to have it in a database if you update it every time you add new scopes to your app.
The Access Token is the authorization from the user to your application to get it's Google Data (calendar in this case). It has a limited lifetime so this is not okay to save in a database.
The Refresh Token is the token that allows your application to get more tokens for a client. It has a limited lifetime as well.
For more info see: Using OAuth 2.0 to Access Google APIs
Every time you change your scopes or add more scopes you have to re-generate the credentials. You have 50 refresh tokens per user account per client, see Token expiration. So having the tokens in a database makes no sense since they are going to get deprecated at some point, if you have 51 clients the 1st token will get deprecated.
Check:
How do you have it set-up on your database
If you renew properly the tokens
If you are using the correct tokens for the users
You can delete all the tokens (NOT the CREDENTIALS) and your current users will only have to go through the consent screen and allow it again, they will not lose the connection.
I asked the question later in a different way. Maybe it was a little more relevant. Perhaps there was a little more information available. What ever the case may be, I discovered how to test things properly.
Look at this question
I am looking for proper way how to implement authentification and authorization.
If I understand it well - this should be realized through "Identity" - it's offering both of these things I need.
My problem is that i can't use a database. I have to use a service (WCF service where our internal DDLs are connected to our system) which is able only Login (I give it user name and password) and after login i can get list of permissons.
I already saw articles how to have custom UserStore, RoleStore, UserManager and SignInManager.. but I am still confused and I don't know how to do it.
Is this even posible through Identity model? If not how I should do it please?
Thank you for every advice.
There are some articles which I already checked:
Microsoft - custom storage providers
Core indentity without entity framework
Sikorsky blog - custom user manager
In fact the WCF service is your authentication service. No need to implement this twice. A call to the service verifies the credentials and returns everything that should be in the access token.
Basically all you have to do is generate an access token using the information from the WCF service. And configure your app to use the created access token.
The flow can be like this: first make a call to the WCF service in order to verify the login and retrieve the information.
public async Task<IActionResult> LoginAsync([FromBody]UserLogin login)
{
var loginInfo = _wcf.LoginUser(login);
if (loginInfo == null)
return Unauthorized();
return Ok(CreateAccessToken(loginInfo));
}
To create an access token:
public class TokenHelper
{
public const string Issuer = "http://www.mywebsite.com/myapp";
public const string Audience = "http://www.mywebsite.com/myapp";
// This should not be hardcoded!
public const string Secret = "My_super_secret";
public AccessToken CreateAccessToken(LoginInfo loginInfo)
{
// Set expiration time of 5 minutes.
DateTime expires = DateTime.UtcNow.AddMinutes(5);
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, loginInfo.UserId),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
// Add custom claims, rolepermissions
if (loginInfo.Permissions != null && loginInfo.Permissions.Any())
loginInfo.Permissions.foreach(p => claims.Add(new Claim("Permission", p)));
if (loginInfo.IsUser)
claims.Add(new Claim(ClaimTypes.Role, "User"));
if (loginInfo.IsAdmin)
claims.Add(new Claim(ClaimTypes.Role, "Admin"));
var token = new JwtSecurityToken(
issuer: Issuer,
audience: Audience,
claims: claims,
expires: expires,
signingCredentials: new SigningCredentials(
new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Secret)),
SecurityAlgorithms.HmacSha256
)
);
return new AccessToken
{
ServerTime = DateTime.UtcNow.ToString("yyyyMMddTHH:mm:ssZ"),
Expires = expires.ToString("yyyyMMddTHH:mm:ssZ"),
Bearer = new JwtSecurityTokenHandler().WriteToken(token)
};
}
}
Where AccessToken is:
public class AccessToken
{
public string ServerTime { get; set; }
public string Expires { get; set; }
public string Bearer { get; set; }
}
And add authentication in your startup:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
RequireExpirationTime = true,
RequireSignedTokens = true,
ValidIssuer = TokenHelper.Issuer,
ValidAudience = TokenHelper.Audience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.ASCII.GetBytes(
TokenHelper.Secret))
};
}
);
-- update --
_issuer, _audience and _secret are from some 'external source'. Meaning that all three are fixed string values, but the source (where the value is set) is variable.
For _issuer you usually use the url of the server that issues the token. Like http://www.mywebsite.com/myapp
The _audience is the application that is meant to accept the token. In this case _issuer and _audience are the same, so you can use the same value.
_secret is, well secret and can be any string, like 'my_super_secret'. This is something you want to stay secret. So you don't hardcode it, but get it from a safe location instead.
I've updated above code in a way so you can test it. Please note that secret should not be hardcoded.
There are millions of guides out there, and none of them seem to do what I need. I am creating an Authentication Server, that simply just needs to issue, and validate/reissue tokens. So I can't create a middleware class to "VALIDATE" the cookie or header. I am simply receiving a POST of the string, and I need to validate the token that way, instead of the Authorize middleware that .net core provides.
My Startup Consists of the only Token Issuer Example I could get working.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
var secretKey = "mysupersecret_secretkey!123";
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));
var options = new TokenProviderOptions
{
// The signing key must match!
Audience = "AllApplications",
SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),
Issuer = "Authentication"
};
app.UseMiddleware<TokenProviderMiddleware>(Microsoft.Extensions.Options.Options.Create(options));
I can use the middleware on creation since I just need to intercept the body for the username and password. The middleware takes in the options from the previous Startup.cs code, checks the Request Path and will Generate the token from the context seen below.
private async Task GenerateToken(HttpContext context)
{
CredentialUser usr = new CredentialUser();
using (var bodyReader = new StreamReader(context.Request.Body))
{
string body = await bodyReader.ReadToEndAsync();
usr = JsonConvert.DeserializeObject<CredentialUser>(body);
}
///get user from Credentials put it in user variable. If null send bad request
var now = DateTime.UtcNow;
// Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
// You can add other claims here, if you want:
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, JsonConvert.SerializeObject(user)),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, now.ToString(), ClaimValueTypes.Integer64)
};
// Create the JWT and write it to a string
var jwt = new JwtSecurityToken(
issuer: _options.Issuer,
audience: _options.Audience,
claims: claims,
notBefore: now,
expires: now.Add(_options.Expiration),
signingCredentials: _options.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
///fill response with jwt
}
This large block of code above will Deserialize the CredentialUser json and then execute a stored procedure that returns the User Object. I will then add three claims, and ship it back.
I am able to successfully generate a jwt, and using an online tool like jwt.io, I put the secret key, and the tool says it is valid, with an object that I could use
{
"sub": " {User_Object_Here} ",
"jti": "96914b3b-74e2-4a68-a248-989f7d126bb1",
"iat": "6/28/2017 4:48:15 PM",
"nbf": 1498668495,
"exp": 1498668795,
"iss": "Authentication",
"aud": "AllApplications"
}
The problem I'm having is understanding how to manually check the claims against the signature. Since this is a server that issues and validates tokens. Setting up the Authorize middleware is not an option, like most guides have. Below I am attempting to Validate the Token.
[Route("api/[controller]")]
public class ValidateController : Controller
{
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Validate(string token)
{
var validationParameters = new TokenProviderOptions()
{
Audience = "AllMyApplications",
SigningCredentials = new
SigningCredentials("mysupersecret_secretkey!123",
SecurityAlgorithms.HmacSha256),
Issuer = "Authentication"
};
var decodedJwt = new JwtSecurityTokenHandler().ReadJwtToken(token);
var valid = new JwtSecurityTokenHandler().ValidateToken(token, //The problem is here
/// I need to be able to pass in the .net TokenValidParameters, even though
/// I have a unique jwt that is TokenProviderOptions. I also don't know how to get my user object out of my claims
}
}
I stole borrowed this code mostly from the ASP.Net Core source code: https://github.com/aspnet/Security/blob/dev/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs#L45
From that code I created this function:
private string Authenticate(string token) {
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
List<Exception> validationFailures = null;
SecurityToken validatedToken;
var validator = new JwtSecurityTokenHandler();
// These need to match the values used to generate the token
TokenValidationParameters validationParameters = new TokenValidationParameters();
validationParameters.ValidIssuer = "http://localhost:5000";
validationParameters.ValidAudience = "http://localhost:5000";
validationParameters.IssuerSigningKey = key;
validationParameters.ValidateIssuerSigningKey = true;
validationParameters.ValidateAudience = true;
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal;
try
{
// This line throws if invalid
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
// If we got here then the token is valid
if (principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
return principal.Claims.Where(c => c.Type == ClaimTypes.Email).First().Value;
}
}
catch (Exception e)
{
_logger.LogError(null, e);
}
}
return String.Empty;
}
The validationParameters need to match those in your GenerateToken function and then it should validate just fine.
I have a webapi which is authenticated using JWT tokens.
I validate using a custom JWT protection. This is as follows;
private const string AudiencePropertyKey = "as:client_id";
private readonly string _issuer = string.Empty;
public CustomJwtFormat(string issuer)
{
_issuer = issuer;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
Client client = null;
string audienceId = data.Properties.Dictionary.ContainsKey(AudiencePropertyKey) ? data.Properties.Dictionary[AudiencePropertyKey] : null;
if (string.IsNullOrWhiteSpace(audienceId)) throw new InvalidOperationException("AuthenticationTicket.Properties does not include the client_id");
using (AuthRepository _repo = new AuthRepository())
{
client = _repo.FindClient(audienceId);
}
if (client == null) throw new InvalidOperationException("ClientId does not exist.");
string symmetricKeyAsBase64 = client.Secret;
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
Access is controlled through a database table, so the user sends across their clientid as a part of the auth service. they are rejected if the clientid doesn't exist in the database, and the ticket is encoded using the secret associated with this db entry and returned to them.
Now I am struggling with the decoding of the JWT on data requests? Now the JWT decodes just fine on jwt.io so I assume there must be someway of decoding using the JwtProtect without requiring a store on the currently dished out JWT tokens? As far as I can see the JwtProtect wants to have the Allowed audiences passed across? (I could so this by returning all from the db but is it really necessary?).
JWT token is just a base64 string, you can freely decode it in multiple ways.
If you want to "unprotect" and validate the ticket you can use System.IdentityModel.Tokens.SecurityTokenHandler.
Check this answer for an example.
BTW, just a personal consideration: the first rule in security is "do not make your own security, but stick with the mass". You will find that following a clear and used path will offer you more support and you will be sure not to mess or to forget something important.