├── .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 |
--------------------------------------------------------------------------------