├── .gitignore ├── API-Auth0.sln ├── API-Auth0 ├── API-Auth0.csproj ├── Controllers │ └── BooksController.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── appsettings.Development.json └── appsettings.json ├── LICENSE ├── README.md └── Test ├── ApplicationTest.cs ├── Test.csproj └── appsettings.json /.gitignore: -------------------------------------------------------------------------------- 1 | /.vs 2 | /JWT1/*.user 3 | /JWT1/bin 4 | /JWT1/obj 5 | /Test/bin 6 | /Test/obj 7 | /API-Auth0/bin 8 | /API-Auth0/obj 9 | /API-Auth0/*.user 10 | -------------------------------------------------------------------------------- /API-Auth0.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2020 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "Test\Test.csproj", "{2C32F0C7-55C8-4B6A-BCE4-A00D5C9D48E5}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "API-Auth0", "API-Auth0\API-Auth0.csproj", "{CFA9D7BD-3030-4B98-B4E9-5544BBE03141}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {2C32F0C7-55C8-4B6A-BCE4-A00D5C9D48E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {2C32F0C7-55C8-4B6A-BCE4-A00D5C9D48E5}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {2C32F0C7-55C8-4B6A-BCE4-A00D5C9D48E5}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {2C32F0C7-55C8-4B6A-BCE4-A00D5C9D48E5}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {CFA9D7BD-3030-4B98-B4E9-5544BBE03141}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {CFA9D7BD-3030-4B98-B4E9-5544BBE03141}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {CFA9D7BD-3030-4B98-B4E9-5544BBE03141}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {CFA9D7BD-3030-4B98-B4E9-5544BBE03141}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {DF210460-8A63-4839-A232-E132CAD04D36} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /API-Auth0/API-Auth0.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | JWT 6 | JWT 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /API-Auth0/Controllers/BooksController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Security.Claims; 7 | 8 | namespace APIAuth0.Controllers 9 | { 10 | [Route("api/[controller]")] 11 | public class BooksController : Controller 12 | { 13 | [HttpGet, Authorize] 14 | public IEnumerable Get() 15 | { 16 | var resultBookList = new Book[] { 17 | new Book { Author = "Ray Bradbury", Title = "Fahrenheit 451", AgeRestriction = false }, 18 | new Book { Author = "Gabriel García Márquez", Title = "One Hundred years of Solitude", AgeRestriction = false }, 19 | new Book { Author = "George Orwell", Title = "1984", AgeRestriction = false }, 20 | new Book { Author = "Anais Nin", Title = "Delta of Venus", AgeRestriction = true } 21 | }; 22 | 23 | return resultBookList; 24 | } 25 | 26 | public class Book 27 | { 28 | public string Author { get; set; } 29 | public string Title { get; set; } 30 | public bool AgeRestriction { get; set; } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /API-Auth0/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace APIAuth0 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /API-Auth0/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:63939/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "api/books", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "JWT1": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "api/books", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | }, 26 | "applicationUrl": "http://localhost:63939/" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /API-Auth0/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication.JwtBearer; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.IdentityModel.Tokens; 7 | using System.Text; 8 | 9 | namespace APIAuth0 10 | { 11 | public class Startup 12 | { 13 | public Startup(IConfiguration configuration) 14 | { 15 | Configuration = configuration; 16 | } 17 | 18 | public IConfiguration Configuration { get; } 19 | 20 | // This method gets called by the runtime. Use this method to add services to the container. 21 | public void ConfigureServices(IServiceCollection services) 22 | { 23 | services.AddAuthentication(options => 24 | { 25 | options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 26 | options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 27 | 28 | }).AddJwtBearer(options => 29 | { 30 | options.Authority = Configuration["Auth0:Authority"]; 31 | options.Audience = Configuration["Auth0:Audience"]; 32 | }); 33 | 34 | services.AddMvc(); 35 | } 36 | 37 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 38 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 39 | { 40 | if (env.IsDevelopment()) 41 | { 42 | app.UseDeveloperExceptionPage(); 43 | } 44 | 45 | app.UseAuthentication(); 46 | 47 | app.UseMvc(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /API-Auth0/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /API-Auth0/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | }, 15 | "Auth0": { 16 | "Authority": "YOUR_AUTH0_DOMAIN", 17 | "Audience": "APP_URL" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Andrea Chiarelli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netcore2-Auth0 2 | This is a .NET Core 2 sample project showing how to secure Web APIs access by using Auth0 services. It is the companion code for the article [Developing Web Apps with ASP.NET Core 2.0 and React - Part 1](https://auth0.com/blog/developing-web-apps-with-asp-dot-net-core-2-dot-0-and-react-part-1/). 3 | 4 | The project defines a Web API application whose authorization is based on Auth0 authorization services. 5 | 6 | 7 | ## Running the project ## 8 | 9 | 10 | The solution contains a _Test_ project with three integration tests validating the application behaviour. 11 | You can run the tests from Visual Studio 2017 or by typing `dotnet test` in a command window. 12 | 13 | If you want to interactively test the application, you can use [Postman](https://www.getpostman.com/) or any other Http client. 14 | 15 | 1. Run the project from Visual Studio 2017 or by typing `dotnet run` in a command window 16 | 2. Launch _Postman_ and make a GET request as follows: 17 | 18 | ``` 19 | GET http://localhost:63939/api/books HTTP/1.1 20 | cache-control: no-cache 21 | Accept: */* 22 | Host: localhost:63939 23 | accept-encoding: gzip, deflate 24 | Connection: keep-alive 25 | ``` 26 | 27 | This should return a 401 HTTP status code (_Unauthorized_) 28 | 29 | 3. Make a POST request like the following: 30 | 31 | ``` 32 | POST http://localhost:63939/api/token HTTP/1.1 33 | cache-control: no-cache 34 | Content-Type: application/json 35 | Accept: */* 36 | Host: localhost:63939 37 | accept-encoding: gzip, deflate 38 | Connection: keep-alive 39 | 40 | {"client_id":"YOUR_CLIENT_ID","client_secret":"YOUR_CLIENT_SECRET","audience":"http://localhost:63939/","grant_type":"client_credentials"} 41 | ``` 42 | 43 | It returns a JSON object like the following: 44 | 45 | ``` 46 | { 47 | "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlEwRXhOamMzUXpCRE9VSkZSRVJCUTBNME1UY3dRekl6TkRkQ1F6WTVRVGMzUXpBNFJrUkVOZyJ9.eyJpc3MiOiJodHRwczovL2FuZHljaGlhcmUuZXUuYXV0aDAuY29tLyIsInN1YiI6InFBUjBjckRGTWhXdTI2T2hmU2M5eTNIQ2pzU1RBUEdNQGNsaWVudHMiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjYzOTM5LyIsImlhdCI6MTUxNjE3MTA5MywiZXhwIjoxNTE2MjU3NDkzLCJhenAiOiJxQVIwY3JERk1oV3UyNk9oZlNjOXkzSENqc1NUQVBHTSIsInNjb3BlIjoicHJvZmlsZSBvcGVuaWQiLCJndHkiOiJjbGllbnQtY3JlZGVudGlhbHMifQ.eyT7tEJluAz3Pb1h64kgvYtGpmi81BFBB5UvsirPfkoCtOzJNgMFmH-nk8kTi5n_IxQH3GexJM5qUdlvvfSc5QY_-fZ0JHX7tCYfBVf0xTtWz5gyiSgIkk8HMfBQqh2juQLWcxVImz79LmCULzDa5j3uYgyBYiPPr0vv5-gRjfMmuLtvpQu329oL8-7FXS2Bun6t7q6MSSL7jfTp_yRIF4T3cOTvtK6n9KtP7854Gks00ecnIbIqdmD_IoYYDGoxzxCAYFU5mI7TjjtP5avMy-uKoz05fR-XIsMsYJJYin3xgsMqk15aybwuJy1wOHJcLPS3J4kCKmP-XgVSEAxDgA", 48 | "expires_in": 86400, 49 | "token_type": "Bearer" 50 | } 51 | ``` 52 | 53 | 4. The following GET request 54 | 55 | ``` 56 | GET http://localhost:63939/api/books HTTP/1.1 57 | cache-control: no-cache 58 | Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlEwRXhOamMzUXpCRE9VSkZSRVJCUTBNME1UY3dRekl6TkRkQ1F6WTVRVGMzUXpBNFJrUkVOZyJ9.eyJpc3MiOiJodHRwczovL2FuZHljaGlhcmUuZXUuYXV0aDAuY29tLyIsInN1YiI6InFBUjBjckRGTWhXdTI2T2hmU2M5eTNIQ2pzU1RBUEdNQGNsaWVudHMiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjYzOTM5LyIsImlhdCI6MTUxNjE3MTA5MywiZXhwIjoxNTE2MjU3NDkzLCJhenAiOiJxQVIwY3JERk1oV3UyNk9oZlNjOXkzSENqc1NUQVBHTSIsInNjb3BlIjoicHJvZmlsZSBvcGVuaWQiLCJndHkiOiJjbGllbnQtY3JlZGVudGlhbHMifQ.eyT7tEJluAz3Pb1h64kgvYtGpmi81BFBB5UvsirPfkoCtOzJNgMFmH-nk8kTi5n_IxQH3GexJM5qUdlvvfSc5QY_-fZ0JHX7tCYfBVf0xTtWz5gyiSgIkk8HMfBQqh2juQLWcxVImz79LmCULzDa5j3uYgyBYiPPr0vv5-gRjfMmuLtvpQu329oL8-7FXS2Bun6t7q6MSSL7jfTp_yRIF4T3cOTvtK6n9KtP7854Gks00ecnIbIqdmD_IoYYDGoxzxCAYFU5mI7TjjtP5avMy-uKoz05fR-XIsMsYJJYin3xgsMqk15aybwuJy1wOHJcLPS3J4kCKmP-XgVSEAxDgA 59 | Accept: */* 60 | Host: localhost:63939 61 | accept-encoding: gzip, deflate 62 | Connection: keep-alive 63 | ``` 64 | 65 | returns the following response: 66 | 67 | ``` 68 | [ 69 | { 70 | "author": "Ray Bradbury", 71 | "title": "Fahrenheit 451", 72 | "ageRestriction": false 73 | }, 74 | { 75 | "author": "Gabriel García Márquez", 76 | "title": "One Hundred years of Solitude", 77 | "ageRestriction": false 78 | }, 79 | { 80 | "author": "George Orwell", 81 | "title": "1984", 82 | "ageRestriction": false 83 | }, 84 | { 85 | "author": "Anais Nin", 86 | "title": "Delta of Venus", 87 | "ageRestriction": true 88 | } 89 | ] 90 | ``` 91 | 92 | -------------------------------------------------------------------------------- /Test/ApplicationTest.cs: -------------------------------------------------------------------------------- 1 | using APIAuth0; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.TestHost; 4 | using Microsoft.Extensions.Configuration; 5 | using Newtonsoft.Json.Linq; 6 | using System.IO; 7 | using System.Net; 8 | using System.Net.Http; 9 | using System.Net.Http.Headers; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using Xunit; 13 | 14 | namespace Test 15 | { 16 | public class ApplicationTest 17 | { 18 | private readonly TestServer _server; 19 | private readonly HttpClient _client; 20 | private IConfiguration _configuration; 21 | public ApplicationTest() 22 | { 23 | _configuration = new ConfigurationBuilder() 24 | .SetBasePath(Path.GetFullPath(@"../../..")) 25 | .AddJsonFile("appsettings.json", optional: false) 26 | .Build(); 27 | 28 | _server = new TestServer(new WebHostBuilder() 29 | .UseStartup() 30 | .UseConfiguration(_configuration)); 31 | _client = _server.CreateClient(); 32 | } 33 | 34 | [Fact] 35 | public async Task UnAuthorizedAccess() 36 | { 37 | var response = await _client.GetAsync("/api/books"); 38 | 39 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 40 | } 41 | 42 | public async Task GetToken() 43 | { 44 | var auth0Client = new HttpClient(); 45 | string token = ""; 46 | var bodyString = $@"{{""client_id"":""{_configuration["Auth0:ClientId"]}"", ""client_secret"":""{_configuration["Auth0:ClientSecret"]}"", ""audience"":""{_configuration["Auth0:Audience"]}"", ""grant_type"":""client_credentials""}}"; 47 | var response = await auth0Client.PostAsync($"{_configuration["Auth0:Authority"]}oauth/token", new StringContent(bodyString, Encoding.UTF8, "application/json")); 48 | 49 | if (response.IsSuccessStatusCode) 50 | { 51 | var responseString = await response.Content.ReadAsStringAsync(); 52 | var responseJson = JObject.Parse(responseString); 53 | token = (string)responseJson["access_token"]; 54 | 55 | } 56 | 57 | return token; 58 | } 59 | 60 | [Fact] 61 | public async Task TestGetToken() 62 | { 63 | var auth0Client = new HttpClient(); 64 | var bodyString = $@"{{""client_id"":""{_configuration["Auth0:ClientId"]}"", ""client_secret"":""{_configuration["Auth0:ClientSecret"]}"", ""audience"":""{_configuration["Auth0:Audience"]}"", ""grant_type"":""client_credentials""}}"; 65 | var response = await auth0Client.PostAsync($"{_configuration["Auth0:Authority"]}oauth/token", new StringContent(bodyString, Encoding.UTF8, "application/json")); 66 | 67 | Assert.Equal(HttpStatusCode.OK, response.StatusCode); 68 | 69 | var responseString = await response.Content.ReadAsStringAsync(); 70 | var responseJson = JObject.Parse(responseString); 71 | Assert.NotNull((string)responseJson["access_token"]); 72 | Assert.Equal("Bearer", (string)responseJson["token_type"]); 73 | } 74 | 75 | [Fact] 76 | public async Task GetBooks() 77 | { 78 | var token = await GetToken(); 79 | 80 | var requestMessage = new HttpRequestMessage(HttpMethod.Get, "/api/books"); 81 | requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); 82 | var booksResponse = await _client.SendAsync(requestMessage); 83 | 84 | Assert.Equal(HttpStatusCode.OK, booksResponse.StatusCode); 85 | 86 | var bookResponseString = await booksResponse.Content.ReadAsStringAsync(); 87 | var bookResponseJson = JArray.Parse(bookResponseString); 88 | Assert.Equal(4, bookResponseJson.Count); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Test/Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Test/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | }, 15 | "Auth0": { 16 | "Authority": "YOUR_AUTH0_DOMAIN", 17 | "Audience": "APP_URL", 18 | "ClientId": "CLIENT_ID", 19 | "ClientSecret": "CLIENT_SECRET" 20 | } 21 | } 22 | --------------------------------------------------------------------------------