Skip to content
This repository was archived by the owner on Apr 8, 2020. It is now read-only.

Commit e44d2df

Browse files
author
Piotrek
committed
Added OpenIddict in C# backed for token authentication
1 parent 9d50061 commit e44d2df

27 files changed

+1623
-8
lines changed

JavaScriptServices.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Aurelia", "templates\Aureli
5050
EndProject
5151
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "KnockoutSpa", "templates\KnockoutSpa\KnockoutSpa.xproj", "{85231B41-6998-49AE-ABD2-5124C83DBEF2}"
5252
EndProject
53+
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.NodeServices.Sockets", "src\Microsoft.AspNetCore.NodeServices.Sockets\Microsoft.AspNetCore.NodeServices.Sockets.xproj", "{05F5AA92-E245-490E-92CA-F7CA25FF2A86}"
54+
EndProject
5355
Global
5456
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5557
Debug|Any CPU = Debug|Any CPU
@@ -120,6 +122,10 @@ Global
120122
{85231B41-6998-49AE-ABD2-5124C83DBEF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
121123
{85231B41-6998-49AE-ABD2-5124C83DBEF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
122124
{85231B41-6998-49AE-ABD2-5124C83DBEF2}.Release|Any CPU.Build.0 = Release|Any CPU
125+
{05F5AA92-E245-490E-92CA-F7CA25FF2A86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
126+
{05F5AA92-E245-490E-92CA-F7CA25FF2A86}.Debug|Any CPU.Build.0 = Debug|Any CPU
127+
{05F5AA92-E245-490E-92CA-F7CA25FF2A86}.Release|Any CPU.ActiveCfg = Release|Any CPU
128+
{05F5AA92-E245-490E-92CA-F7CA25FF2A86}.Release|Any CPU.Build.0 = Release|Any CPU
123129
EndGlobalSection
124130
GlobalSection(SolutionProperties) = preSolution
125131
HideSolutionNode = FALSE
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="14.0.25420" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25420</VisualStudioVersion>
5+
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
6+
</PropertyGroup>
7+
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
8+
<PropertyGroup Label="Globals">
9+
<ProjectGuid>05f5aa92-e245-490e-92ca-f7ca25ff2a86</ProjectGuid>
10+
<RootNamespace>Microsoft.AspNetCore.NodeServices.Sockets</RootNamespace>
11+
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
12+
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
13+
</PropertyGroup>
14+
15+
<PropertyGroup>
16+
<SchemaVersion>2.0</SchemaVersion>
17+
</PropertyGroup>
18+
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
19+
</Project>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using Angular2Spa.Models;
2+
using Microsoft.AspNetCore.Authorization;
3+
using Microsoft.AspNetCore.Identity;
4+
using Microsoft.AspNetCore.Mvc;
5+
using System.Threading.Tasks;
6+
using DefinicjePL.ViewModels;
7+
using Microsoft.Extensions.Logging;
8+
9+
namespace Angular2Spa.Controllers
10+
{
11+
[Authorize]
12+
[Produces("application/json")]
13+
public class AccountController : Controller
14+
{
15+
private readonly UserManager<ApplicationUser> _userManager;
16+
private readonly SignInManager<ApplicationUser> _signInManager;
17+
private readonly ILogger _logger;
18+
19+
public AccountController(
20+
UserManager<ApplicationUser> userManager,
21+
SignInManager<ApplicationUser> signInManager,
22+
ILoggerFactory loggerFactory)
23+
{
24+
_userManager = userManager;
25+
_signInManager = signInManager;
26+
_logger = loggerFactory.CreateLogger<AccountController>();
27+
}
28+
29+
// POST: api/Account/Register
30+
[HttpPost]
31+
[AllowAnonymous]
32+
[Route("api/Account/Register")]
33+
public async Task<IActionResult> Register([FromBody] RegisterViewModel model)
34+
{
35+
if (ModelState.IsValid)
36+
{
37+
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
38+
var result = await _userManager.CreateAsync(user, model.Password);
39+
if (result.Succeeded)
40+
{
41+
//await _signInManager.SignInAsync(user, isPersistent: false);
42+
//_logger.LogInformation(3, "User created a new account with password.");
43+
return Ok();
44+
}
45+
else
46+
{
47+
return BadRequest(result.Errors);
48+
}
49+
}
50+
else
51+
{
52+
return BadRequest("Model is not valid");
53+
}
54+
}
55+
}
56+
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
using System.Diagnostics;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using AspNet.Security.OpenIdConnect.Extensions;
5+
using AspNet.Security.OpenIdConnect.Primitives;
6+
using AspNet.Security.OpenIdConnect.Server;
7+
using Microsoft.AspNetCore.Authentication;
8+
using Microsoft.AspNetCore.Authorization;
9+
using Microsoft.AspNetCore.Http.Authentication;
10+
using Microsoft.AspNetCore.Identity;
11+
using Microsoft.AspNetCore.Mvc;
12+
using OpenIddict.Core;
13+
using OpenIddict.Models;
14+
using Angular2Spa.Models;
15+
using Mvc.Server.ViewModels.Shared;
16+
using Mvc.Server.ViewModels.Authorization;
17+
using Mvc.Server.Helpers;
18+
19+
namespace Angular2Spa.Controllers
20+
{
21+
public class AuthorizationController : Controller
22+
{
23+
private readonly OpenIddictApplicationManager<OpenIddictApplication> _applicationManager;
24+
private readonly SignInManager<ApplicationUser> _signInManager;
25+
private readonly UserManager<ApplicationUser> _userManager;
26+
27+
public AuthorizationController(
28+
OpenIddictApplicationManager<OpenIddictApplication> applicationManager,
29+
SignInManager<ApplicationUser> signInManager,
30+
UserManager<ApplicationUser> userManager)
31+
{
32+
_applicationManager = applicationManager;
33+
_signInManager = signInManager;
34+
_userManager = userManager;
35+
}
36+
37+
[Authorize, HttpGet("~/connect/authorize")]
38+
public async Task<IActionResult> Authorize(OpenIdConnectRequest request)
39+
{
40+
Debug.Assert(request.IsAuthorizationRequest(),
41+
"The OpenIddict binder for ASP.NET Core MVC is not registered. " +
42+
"Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");
43+
44+
// Retrieve the application details from the database.
45+
var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted);
46+
if (application == null)
47+
{
48+
return View("Error", new ErrorViewModel
49+
{
50+
Error = OpenIdConnectConstants.Errors.InvalidClient,
51+
ErrorDescription = "Details concerning the calling client application cannot be found in the database"
52+
});
53+
}
54+
55+
// Flow the request_id to allow OpenIddict to restore
56+
// the original authorization request from the cache.
57+
return View(new AuthorizeViewModel
58+
{
59+
ApplicationName = application.DisplayName,
60+
RequestId = request.RequestId,
61+
Scope = request.Scope
62+
});
63+
}
64+
65+
[Authorize, FormValueRequired("submit.Accept")]
66+
[HttpPost("~/connect/authorize"), ValidateAntiForgeryToken]
67+
public async Task<IActionResult> Accept(OpenIdConnectRequest request)
68+
{
69+
Debug.Assert(request.IsAuthorizationRequest(),
70+
"The OpenIddict binder for ASP.NET Core MVC is not registered. " +
71+
"Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");
72+
73+
// Retrieve the profile of the logged in user.
74+
var user = await _userManager.GetUserAsync(User);
75+
if (user == null)
76+
{
77+
return View("Error", new ErrorViewModel
78+
{
79+
Error = OpenIdConnectConstants.Errors.ServerError,
80+
ErrorDescription = "An internal error has occurred"
81+
});
82+
}
83+
84+
// Create a new authentication ticket.
85+
var ticket = await CreateTicketAsync(request, user);
86+
87+
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
88+
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
89+
}
90+
91+
[Authorize, FormValueRequired("submit.Deny")]
92+
[HttpPost("~/connect/authorize"), ValidateAntiForgeryToken]
93+
public IActionResult Deny()
94+
{
95+
// Notify OpenIddict that the authorization grant has been denied by the resource owner
96+
// to redirect the user agent to the client application using the appropriate response_mode.
97+
return Forbid(OpenIdConnectServerDefaults.AuthenticationScheme);
98+
}
99+
100+
[HttpGet("~/connect/logout")]
101+
public IActionResult Logout(OpenIdConnectRequest request)
102+
{
103+
// Flow the request_id to allow OpenIddict to restore
104+
// the original logout request from the distributed cache.
105+
return View(new LogoutViewModel
106+
{
107+
RequestId = request.RequestId,
108+
});
109+
}
110+
111+
[HttpPost("~/connect/logout"), ValidateAntiForgeryToken]
112+
public async Task<IActionResult> Logout()
113+
{
114+
// Ask ASP.NET Core Identity to delete the local and external cookies created
115+
// when the user agent is redirected from the external identity provider
116+
// after a successful authentication flow (e.g Google or Facebook).
117+
await _signInManager.SignOutAsync();
118+
119+
// Returning a SignOutResult will ask OpenIddict to redirect the user agent
120+
// to the post_logout_redirect_uri specified by the client application.
121+
return SignOut(OpenIdConnectServerDefaults.AuthenticationScheme);
122+
}
123+
124+
[HttpPost("~/api/connect/token"), Produces("application/json")]
125+
public async Task<IActionResult> Exchange(OpenIdConnectRequest request)
126+
{
127+
Debug.Assert(request.IsTokenRequest(),
128+
"The OpenIddict binder for ASP.NET Core MVC is not registered. " +
129+
"Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");
130+
131+
if (request.IsAuthorizationCodeGrantType())
132+
{
133+
// Retrieve the claims principal stored in the authorization code.
134+
var info = await HttpContext.Authentication.GetAuthenticateInfoAsync(
135+
OpenIdConnectServerDefaults.AuthenticationScheme);
136+
137+
// Retrieve the user profile corresponding to the authorization code.
138+
var user = await _userManager.GetUserAsync(info.Principal);
139+
if (user == null)
140+
{
141+
return BadRequest(new OpenIdConnectResponse
142+
{
143+
Error = OpenIdConnectConstants.Errors.InvalidGrant,
144+
ErrorDescription = "The authorization code is no longer valid."
145+
});
146+
}
147+
148+
// Ensure the user is still allowed to sign in.
149+
if (!await _signInManager.CanSignInAsync(user))
150+
{
151+
return BadRequest(new OpenIdConnectResponse
152+
{
153+
Error = OpenIdConnectConstants.Errors.InvalidGrant,
154+
ErrorDescription = "The user is no longer allowed to sign in."
155+
});
156+
}
157+
158+
// Create a new authentication ticket, but reuse the properties stored
159+
// in the authorization code, including the scopes originally granted.
160+
var ticket = await CreateTicketAsync(request, user, info.Properties);
161+
162+
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
163+
}
164+
165+
return BadRequest(new OpenIdConnectResponse
166+
{
167+
Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
168+
ErrorDescription = "The specified grant type is not supported."
169+
});
170+
}
171+
172+
private async Task<AuthenticationTicket> CreateTicketAsync(
173+
OpenIdConnectRequest request, ApplicationUser user,
174+
AuthenticationProperties properties = null)
175+
{
176+
// Create a new ClaimsPrincipal containing the claims that
177+
// will be used to create an id_token, a token or a code.
178+
var principal = await _signInManager.CreateUserPrincipalAsync(user);
179+
180+
// Note: by default, claims are NOT automatically included in the access and identity tokens.
181+
// To allow OpenIddict to serialize them, you must attach them a destination, that specifies
182+
// whether they should be included in access tokens, in identity tokens or in both.
183+
184+
foreach (var claim in principal.Claims)
185+
{
186+
// In this sample, every claim is serialized in both the access and the identity tokens.
187+
// In a real world application, you'd probably want to exclude confidential claims
188+
// or apply a claims policy based on the scopes requested by the client application.
189+
claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken,
190+
OpenIdConnectConstants.Destinations.IdentityToken);
191+
}
192+
193+
// Create a new authentication ticket holding the user identity.
194+
var ticket = new AuthenticationTicket(principal, properties,
195+
OpenIdConnectServerDefaults.AuthenticationScheme);
196+
197+
if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType())
198+
{
199+
// Set the list of scopes granted to the client application.
200+
// Note: the offline_access scope must be granted
201+
// to allow OpenIddict to return a refresh token.
202+
ticket.SetScopes(new[] {
203+
OpenIdConnectConstants.Scopes.OpenId,
204+
OpenIdConnectConstants.Scopes.Email,
205+
OpenIdConnectConstants.Scopes.Profile,
206+
OpenIddictConstants.Scopes.Roles
207+
}.Intersect(request.GetScopes()));
208+
}
209+
210+
return ticket;
211+
}
212+
}
213+
}

templates/Angular2Spa/Controllers/HomeController.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq;
44
using System.Threading.Tasks;
55
using Microsoft.AspNetCore.Mvc;
6+
using Microsoft.AspNetCore.Authorization;
67

78
namespace WebApplicationBasic.Controllers
89
{
@@ -17,5 +18,12 @@ public IActionResult Error()
1718
{
1819
return View();
1920
}
21+
22+
[HttpGet]
23+
[Authorize]
24+
public string GetDataThatNeedAuth()
25+
{
26+
return "If you see this message, that means you're logged in";
27+
}
2028
}
2129
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using Microsoft.AspNetCore.Mvc.Abstractions;
3+
using Microsoft.AspNetCore.Mvc.ActionConstraints;
4+
using Microsoft.AspNetCore.Routing;
5+
6+
namespace Mvc.Server.Helpers {
7+
public sealed class FormValueRequiredAttribute : ActionMethodSelectorAttribute {
8+
private readonly string _name;
9+
10+
public FormValueRequiredAttribute(string name) {
11+
_name = name;
12+
}
13+
14+
public override bool IsValidForRequest(RouteContext context, ActionDescriptor action) {
15+
if (string.Equals(context.HttpContext.Request.Method, "GET", StringComparison.OrdinalIgnoreCase) ||
16+
string.Equals(context.HttpContext.Request.Method, "HEAD", StringComparison.OrdinalIgnoreCase) ||
17+
string.Equals(context.HttpContext.Request.Method, "DELETE", StringComparison.OrdinalIgnoreCase) ||
18+
string.Equals(context.HttpContext.Request.Method, "TRACE", StringComparison.OrdinalIgnoreCase)) {
19+
return false;
20+
}
21+
22+
if (string.IsNullOrEmpty(context.HttpContext.Request.ContentType)) {
23+
return false;
24+
}
25+
26+
if (!context.HttpContext.Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) {
27+
return false;
28+
}
29+
30+
return !string.IsNullOrEmpty(context.HttpContext.Request.Form[_name]);
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)