Skip to content

Commit

Permalink
Initial solution.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ian-Hodges committed Mar 21, 2022
0 parents commit 616b013
Show file tree
Hide file tree
Showing 12 changed files with 362 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/appsettings.Development.json
/.vs/oauthy
/bin/Debug/net5.0
/obj
/oauthy.csproj.user
10 changes: 10 additions & 0 deletions AzureAdOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace oauthy
{
public class AzureAdOptions
{
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string Instance { get; set; }
public string TenantId { get; set; }
}
}
42 changes: 42 additions & 0 deletions AzureAdServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using oauthy;
using System;

public static class AzureAdServiceCollectionExtensions
{
public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder)
=> builder.AddAzureAdBearer(_ => { });

public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
{
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<JwtBearerOptions>, ConfigureAzureOptions>();
builder.AddJwtBearer();
return builder;
}

private class ConfigureAzureOptions : IConfigureNamedOptions<JwtBearerOptions>
{
private readonly AzureAdOptions _azureOptions;

public ConfigureAzureOptions(IOptions<AzureAdOptions> azureOptions)
{
_azureOptions = azureOptions.Value;
}

public void Configure(string name, JwtBearerOptions options)
{
options.TokenValidationParameters.ValidAudiences = new string[] { $"api://{_azureOptions.ClientId}", _azureOptions.ClientId };
options.Authority = $"{_azureOptions.Instance}{_azureOptions.TenantId}";
options.Events = JwtBearerMiddlewareDiagnostics.Subscribe(options.Events);
}

public void Configure(JwtBearerOptions options)
{
Configure(Options.DefaultName, options);
}
}
}
40 changes: 40 additions & 0 deletions Controllers/WeatherForecastController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;

namespace oauthy.Controllers
{
[ApiController]
[Route("[controller]")]
[Authorize(Roles = "OuathyAppRole")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

private readonly ILogger<WeatherForecastController> _logger;

public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}

[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
}
91 changes: 91 additions & 0 deletions JwtBearerMiddlewareDiagnostics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace oauthy
{
/// <summary>
/// Diagnostics for the JwtBearer middleware (used in Web APIs)
/// </summary>
public class JwtBearerMiddlewareDiagnostics
{
/// <summary>
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed.
/// </summary>
static Func<AuthenticationFailedContext, Task> onAuthenticationFailed;

/// <summary>
/// Invoked when a protocol message is first received.
/// </summary>
static Func<MessageReceivedContext, Task> onMessageReceived;

/// <summary>
/// Invoked after the security token has passed validation and a ClaimsIdentity has been generated.
/// </summary>
static Func<TokenValidatedContext, Task> onTokenValidated;

/// <summary>
/// Invoked before a challenge is sent back to the caller.
/// </summary>
static Func<JwtBearerChallengeContext, Task> onChallenge;

/// <summary>
/// Subscribes to all the JwtBearer events, to help debugging, while
/// preserving the previous handlers (which are called)
/// </summary>
/// <param name="events">Events to subscribe to</param>
public static JwtBearerEvents Subscribe(JwtBearerEvents events)
{
if (events == null)
{
events = new JwtBearerEvents();
}

onAuthenticationFailed = events.OnAuthenticationFailed;
events.OnAuthenticationFailed = OnAuthenticationFailed;

onMessageReceived = events.OnMessageReceived;
events.OnMessageReceived = OnMessageReceived;

onTokenValidated = events.OnTokenValidated;
events.OnTokenValidated = OnTokenValidated;

onChallenge = events.OnChallenge;
events.OnChallenge = OnChallenge;

return events;
}

static async Task OnMessageReceived(MessageReceivedContext context)
{
Debug.WriteLine($"1. Begin {nameof(OnMessageReceived)}");
// Place a breakpoint here and examine the bearer token (context.Request.Headers.HeaderAuthorization / context.Request.Headers["Authorization"])
// Use https://jwt.ms to decode the token and observe claims
await onMessageReceived(context);
Debug.WriteLine($"1. End - {nameof(OnMessageReceived)}");
}

static async Task OnAuthenticationFailed(AuthenticationFailedContext context)
{
Debug.WriteLine($"99. Begin {nameof(OnAuthenticationFailed)}");
// Place a breakpoint here and examine context.Exception
await onAuthenticationFailed(context);
Debug.WriteLine($"99. End - {nameof(OnAuthenticationFailed)}");
}

static async Task OnTokenValidated(TokenValidatedContext context)
{
Debug.WriteLine($"2. Begin {nameof(OnTokenValidated)}");
await onTokenValidated(context);
Debug.WriteLine($"2. End - {nameof(OnTokenValidated)}");
}

static async Task OnChallenge(JwtBearerChallengeContext context)
{
Debug.WriteLine($"55. Begin {nameof(OnChallenge)}");
await onChallenge(context);
Debug.WriteLine($"55. End - {nameof(OnChallenge)}");
}
}
}
26 changes: 26 additions & 0 deletions Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace oauthy
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
31 changes: 31 additions & 0 deletions Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:49265",
"sslPort": 44352
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"oauthy": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
55 changes: 55 additions & 0 deletions Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;

namespace oauthy
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{

services.AddControllers();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "oauthy", Version = "v1" });
});
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "oauthy v1"));
}

app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
15 changes: 15 additions & 0 deletions WeatherForecast.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace oauthy
{
public class WeatherForecast
{
public DateTime Date { get; set; }

public int TemperatureC { get; set; }

public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

public string Summary { get; set; }
}
}
10 changes: 10 additions & 0 deletions appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
12 changes: 12 additions & 0 deletions oauthy.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.15" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
</ItemGroup>

</Project>
25 changes: 25 additions & 0 deletions oauthy.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31613.86
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "oauthy", "oauthy.csproj", "{2B831538-6E96-4C11-AF3E-408A13828C33}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2B831538-6E96-4C11-AF3E-408A13828C33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B831538-6E96-4C11-AF3E-408A13828C33}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B831538-6E96-4C11-AF3E-408A13828C33}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B831538-6E96-4C11-AF3E-408A13828C33}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7FB810AD-9C28-415F-8931-BCFB5D0D40DA}
EndGlobalSection
EndGlobal

0 comments on commit 616b013

Please sign in to comment.