Skip to content

Commit

Permalink
Merge pull request #6 from AzureAD/security
Browse files Browse the repository at this point in the history
Added token validation code for development and for publishing
  • Loading branch information
marcusca10 authored Feb 25, 2020
2 parents a074698 + b115958 commit 8f12451
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 68 deletions.
67 changes: 67 additions & 0 deletions Microsoft.SCIM.WebHostSample/Controllers/TokenController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.SCIM.WebHostSample.Controllers
{
// Controller for generating a bearer token for authorization during testing.
// This is not meant to replace proper Oauth for authentication purposes.
[Route("scim/token")]
[ApiController]
public class TokenController : ControllerBase
{
private readonly IConfiguration _configuration;

private const int defaultTokenExpiration = 120;

public TokenController(IConfiguration Configuration)
{
_configuration = Configuration;
}

private string GenerateJSONWebToken()
{
// Create token key
SymmetricSecurityKey securityKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this._configuration["Token:IssuerSigningKey"]));
SigningCredentials credentials =
new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

// Set token expiration
DateTime startTime = DateTime.UtcNow;
DateTime expiryTime;
double tokenExpiration;
if (double.TryParse(this._configuration["Token:TokenLifetimeInMins"], out tokenExpiration))
expiryTime = startTime.AddMinutes(tokenExpiration);
else
expiryTime = startTime.AddMinutes(defaultTokenExpiration);

// Generate the token
JwtSecurityToken token =
new JwtSecurityToken(
this._configuration["Token:TokenIssuer"],
this._configuration["Token:TokenAudience"],
null,
notBefore: startTime,
expires: expiryTime,
signingCredentials: credentials);

string result = new JwtSecurityTokenHandler().WriteToken(token);
return result;
}

[HttpGet]
public ActionResult Get()
{
string tokenString = this.GenerateJSONWebToken();
return this.Ok(new { token = tokenString });
}

}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
Expand Down
92 changes: 73 additions & 19 deletions Microsoft.SCIM.WebHostSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
Expand All @@ -17,11 +18,17 @@ namespace Microsoft.SCIM.WebHostSample
{
public class Startup
{
private readonly IWebHostEnvironment _env;
private readonly IConfiguration _configuration;

public IMonitor MonitoringBehavior { get; set; }
public IProvider ProviderBehavior { get; set; }

public Startup()
public Startup(IWebHostEnvironment env, IConfiguration configuration)
{
this._env = env;
this._configuration = configuration;

this.MonitoringBehavior = new ConsoleMonitor();
this.ProviderBehavior = new InMemoryProvider();
}
Expand All @@ -30,26 +37,62 @@ public Startup()
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
if (_env.IsDevelopment())
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
// Development environment code
// Validation for bearer token for authorization used during testing.
// NOTE: It's not recommended to use this code in production, it is not meant to replace proper OAuth authentication.
// This option is primarily available for testing purposes.
services.AddAuthentication(options =>
{
options.TokenValidationParameters =
new TokenValidationParameters
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters =
new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = false,
ValidIssuer = this._configuration["Token:TokenIssuer"],
ValidAudience = this._configuration["Token:TokenAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this._configuration["Token:IssuerSigningKey"]))
};
});
}
else
{
// Leave the optional Secret Token field blank
// Azure AD includes an OAuth bearer token issued from Azure AD with each request
// The following code validates the Azure AD-issued token
// NOTE: It's not recommended to leave this field blank and rely on a token generated by Azure AD.
// This option is primarily available for testing purposes.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = this._configuration["Token:TokenIssuer"];
options.Audience = this._configuration["Token:TokenAudience"];
options.Events = new JwtBearerEvents
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = false,
ValidIssuer = ServiceConstants.TokenIssuer,
ValidAudience = ServiceConstants.TokenAudience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ServiceConstants.TokenIssuer))
OnTokenValidated = context =>
{
// NOTE: You can optionally take action when the OAuth 2.0 bearer token was validated.

return Task.CompletedTask;
},
OnAuthenticationFailed = AuthenticationFailed
};
});
});
}

services.AddControllers().AddNewtonsoftJson();

Expand All @@ -58,9 +101,9 @@ public void ConfigureServices(IServiceCollection services)
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
public void Configure(IApplicationBuilder app)
{
if (env.IsDevelopment())
if (_env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
Expand All @@ -78,5 +121,16 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
endpoints.MapDefaultControllerRoute();
});
}

private Task AuthenticationFailed(AuthenticationFailedContext arg)
{
// For debugging purposes only!
var s = $"{{AuthenticationFailed: '{arg.Exception.Message}'}}";

arg.Response.ContentLength = s.Length;
arg.Response.Body.WriteAsync(Encoding.UTF8.GetBytes(s), 0, s.Length);

return Task.FromException(arg.Exception);
}
}
}
6 changes: 6 additions & 0 deletions Microsoft.SCIM.WebHostSample/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,11 @@
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Token": {
"TokenAudience": "Microsoft.Security.Bearer",
"TokenIssuer": "Microsoft.Security.Bearer",
"IssuerSigningKey": "A1B2C3D4E5F6A1B2C3D4E5F6",
"TokenLifetimeInMins": "120"
}
}
4 changes: 4 additions & 0 deletions Microsoft.SCIM.WebHostSample/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Token": {
"TokenAudience": "8adf8e6e-67b2-4cf2-a259-e3dc5476c621",
"TokenIssuer": "https://sts.windows.net/<tenant_id>/"
},
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
namespace Microsoft.SCIM
{
using System;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[Route(ServiceConstants.RouteGroups)]
[Authorize]
[ApiController]
public sealed class GroupsController : ControllerTemplate<Core2Group>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,48 +11,48 @@ namespace Microsoft.SCIM
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;

// Controller for generating a bearer token for authorization during testing.
// This is not meant to replace proper Oauth for authentication purposes.
[Route(ServiceConstants.RouteToken)]
[ApiController]
public class KeyController : ControllerTemplate
{
private const int TokenLifetimeInMins = 120;

public KeyController(IProvider provider, IMonitor monitor)
: base(provider, monitor)
{
}

private static string GenerateJSONWebToken()
{
SymmetricSecurityKey securityKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ServiceConstants.TokenIssuer));
SigningCredentials credentials =
new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

DateTime startTime = DateTime.UtcNow;
DateTime expiryTime = startTime.AddMinutes(KeyController.TokenLifetimeInMins);

JwtSecurityToken token =
new JwtSecurityToken(
ServiceConstants.TokenIssuer,
ServiceConstants.TokenAudience,
null,
notBefore: startTime,
expires: expiryTime,
signingCredentials: credentials);

string result = new JwtSecurityTokenHandler().WriteToken(token);
return result;
}

[HttpGet]
public ActionResult Get()
{
string tokenString = KeyController.GenerateJSONWebToken();
return this.Ok(new { token = tokenString });
}

}
//// Controller for generating a bearer token for authorization during testing.
//// This is not meant to replace proper Oauth for authentication purposes.
//[Route(ServiceConstants.RouteToken)]
//[ApiController]
//public class KeyController : ControllerTemplate
//{
// private const int TokenLifetimeInMins = 120;

// public KeyController(IProvider provider, IMonitor monitor)
// : base(provider, monitor)
// {
// }

// private static string GenerateJSONWebToken()
// {
// SymmetricSecurityKey securityKey =
// new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ServiceConstants.TokenIssuer));
// SigningCredentials credentials =
// new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

// DateTime startTime = DateTime.UtcNow;
// DateTime expiryTime = startTime.AddMinutes(KeyController.TokenLifetimeInMins);

// JwtSecurityToken token =
// new JwtSecurityToken(
// ServiceConstants.TokenIssuer,
// ServiceConstants.TokenAudience,
// null,
// notBefore: startTime,
// expires: expiryTime,
// signingCredentials: credentials);

// string result = new JwtSecurityTokenHandler().WriteToken(token);
// return result;
// }

// [HttpGet]
// public ActionResult Get()
// {
// string tokenString = KeyController.GenerateJSONWebToken();
// return this.Ok(new { token = tokenString });
// }

//}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
namespace Microsoft.SCIM
{
using System;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[Route(ServiceConstants.RouteUsers)]
[Authorize]
[ApiController]
public sealed class UsersController : ControllerTemplate<Core2EnterpriseUser>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ namespace Microsoft.SCIM
public static class ServiceConstants
{
public const string PathSegmentResourceTypes = "ResourceTypes";
public const string PathSegmentToken = "token";
//public const string PathSegmentToken = "token";
public const string PathSegmentSchemas = "Schemas";
public const string PathSegmentServiceProviderConfiguration = "ServiceProviderConfig";

public const string RouteGroups = SchemaConstants.PathInterface + ServiceConstants.SeparatorSegments + ProtocolConstants.PathGroups;
public const string RouteResourceTypes = SchemaConstants.PathInterface + ServiceConstants.SeparatorSegments + ServiceConstants.PathSegmentResourceTypes;
public const string RouteSchemas = SchemaConstants.PathInterface + ServiceConstants.SeparatorSegments + ServiceConstants.PathSegmentSchemas;
public const string RouteServiceConfiguration = SchemaConstants.PathInterface + ServiceConstants.SeparatorSegments + ServiceConstants.PathSegmentServiceProviderConfiguration;
public const string RouteToken = SchemaConstants.PathInterface + ServiceConstants.SeparatorSegments + ServiceConstants.PathSegmentToken;
//public const string RouteToken = SchemaConstants.PathInterface + ServiceConstants.SeparatorSegments + ServiceConstants.PathSegmentToken;
public const string RouteUsers = SchemaConstants.PathInterface + ServiceConstants.SeparatorSegments + ProtocolConstants.PathUsers;

public const string SeparatorSegments = "/";

public const string TokenAudience = "Microsoft.Security.Bearer";
public const string TokenIssuer = "Microsoft.Security.Bearer";
//public const string TokenAudience = "Microsoft.Security.Bearer";
//public const string TokenIssuer = "Microsoft.Security.Bearer";
}
}

0 comments on commit 8f12451

Please sign in to comment.