Created
September 30, 2015 17:02
-
-
Save ilmax/a449adcfbb0f1b16dff0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class AuthorizationController : Controller | |
{ | |
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)] | |
[Route("~/connect/authorize")] | |
public async Task<ActionResult> Authorize(CancellationToken cancellationToken) | |
{ | |
// Note: when a fatal error occurs during the request processing, an OpenID Connect response | |
// is prematurely forged and added to the OWIN context by OpenIdConnectServerHandler. | |
// In this case, the OpenID Connect request is null and cannot be used. | |
// When the user agent can be safely redirected to the client application, | |
// OpenIdConnectServerHandler automatically handles the error and MVC is not invoked. | |
// You can safely remove this part and let Owin.Security.OpenIdConnect.Server automatically | |
// handle the unrecoverable errors by switching ApplicationCanDisplayErrors to false in Startup.cs | |
var response = OwinContext.GetOpenIdConnectResponse(); | |
if (response != null) | |
{ | |
return View("Error", response); | |
} | |
// Extract the authorization request from the cache, the query string or the request form. | |
var request = OwinContext.GetOpenIdConnectRequest(); | |
if (request == null) | |
{ | |
return View("Error", new OpenIdConnectMessage | |
{ | |
Error = "invalid_request", | |
ErrorDescription = "An internal error has occurred" | |
}); | |
} | |
// Note: authentication could be theorically enforced at the filter level via AuthorizeAttribute | |
// but this authorization endpoint accepts both GET and POST requests while the cookie middleware | |
// only uses 302 responses to redirect the user agent to the login page, making it incompatible with POST. | |
// To work around this limitation, the OpenID Connect request is saved in the cache and will be | |
// restored in the other "Authorize" method, after the authentication process has been completed. | |
if (User.Identity == null || !User.Identity.IsAuthenticated) | |
{ | |
return View("Login", new LoginViewModel(OwinContext.Authentication.GetExternalProviders())); | |
//return RedirectToAction("SignIn", "Authentication", new { | |
// returnUrl = Url.Action("Authorize", new { | |
// unique_id = request.GetUniqueIdentifier() | |
// }) | |
//}); | |
} | |
// Note: Owin.Security.OpenIdConnect.Server automatically ensures an application | |
// corresponds to the client_id specified in the authorization request using | |
// IOpenIdConnectServerProvider.ValidateClientRedirectUri (see AuthorizationProvider.cs). | |
// In theory, this null check is thus not strictly necessary. That said, a race condition | |
// and a null reference exception could appear here if you manually removed the application | |
// details from the database after the initial check made by Owin.Security.OpenIdConnect.Server. | |
var application = await GetApplicationAsync(request.ClientId, cancellationToken); | |
if (application == null) | |
{ | |
return View("Error", new OpenIdConnectMessage | |
{ | |
Error = "invalid_client", | |
ErrorDescription = "Details concerning the calling client application cannot be found in the database" | |
}); | |
} | |
// Note: in a real world application, you'd probably prefer creating a specific view model. | |
return View("Authorize", Tuple.Create(request, application)); | |
} | |
[Authorize, HttpPost, Route("~/connect/authorize/accept"), ValidateAntiForgeryToken] | |
public async Task<ActionResult> Accept(CancellationToken cancellationToken) | |
{ | |
// Extract the authorization request from the cache, the query string or the request form. | |
var request = OwinContext.GetOpenIdConnectRequest(); | |
if (request == null) | |
{ | |
return View("Error", new OpenIdConnectMessage | |
{ | |
Error = "invalid_request", | |
ErrorDescription = "An internal error has occurred" | |
}); | |
} | |
// Create a new ClaimsIdentity containing the claims that | |
// will be used to create an id_token, a token or a code. | |
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationType); | |
foreach (var claim in OwinContext.Authentication.User.Claims) | |
{ | |
// Allow ClaimTypes.Name to be added in the id_token. | |
// ClaimTypes.NameIdentifier is automatically added, even if its | |
// destination is not defined or doesn't include "id_token". | |
// The other claims won't be visible for the client application. | |
if (claim.Type == ClaimTypes.Name) | |
{ | |
claim.WithDestination("id_token") | |
.WithDestination("token"); | |
} | |
identity.AddClaim(claim); | |
} | |
// Note: Owin.Security.OpenIdConnect.Server automatically ensures an application | |
// corresponds to the client_id specified in the authorization request using | |
// IOpenIdConnectServerProvider.ValidateClientRedirectUri (see AuthorizationProvider.cs). | |
// In theory, this null check is thus not strictly necessary. That said, a race condition | |
// and a null reference exception could appear here if you manually removed the application | |
// details from the database after the initial check made by Owin.Security.OpenIdConnect.Server. | |
var application = await GetApplicationAsync(request.ClientId, CancellationToken.None); | |
if (application == null) | |
{ | |
return View("Error", new OpenIdConnectMessage | |
{ | |
Error = "invalid_client", | |
ErrorDescription = "Details concerning the calling client application cannot be found in the database" | |
}); | |
} | |
// Create a new ClaimsIdentity containing the claims associated with the application. | |
// Note: setting identity.Actor is not mandatory but can be useful to access | |
// the whole delegation chain from the resource server (see ResourceController.cs). | |
identity.Actor = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationType); | |
identity.Actor.AddClaim(ClaimTypes.NameIdentifier, application.ApplicationID); | |
identity.Actor.AddClaim(ClaimTypes.Name, application.DisplayName, destination: "id_token token"); | |
// This call will instruct Owin.Security.OpenIdConnect.Server to serialize | |
// the specified identity to build appropriate tokens (id_token and token). | |
// Note: you should always make sure the identities you return contain either | |
// a 'sub' or a 'ClaimTypes.NameIdentifier' claim. In this case, the returned | |
// identities always contain the name identifier returned by the external provider. | |
OwinContext.Authentication.SignIn(identity); | |
return new HttpStatusCodeResult(200); | |
} | |
[Authorize, HttpPost, Route("~/connect/authorize/deny"), ValidateAntiForgeryToken] | |
public ActionResult Deny(CancellationToken cancellationToken) | |
{ | |
// Extract the authorization request from the cache, the query string or the request form. | |
var request = OwinContext.GetOpenIdConnectRequest(); | |
if (request == null) | |
{ | |
return View("Error", new OpenIdConnectMessage | |
{ | |
Error = "invalid_request", | |
ErrorDescription = "An internal error has occurred" | |
}); | |
} | |
// Notify Owin.Security.OpenIdConnect.Server that the authorization grant has been denied. | |
// Note: OpenIdConnectServerHandler will automatically take care of redirecting | |
// the user agent to the client application using the appropriate response_mode. | |
OwinContext.SetOpenIdConnectResponse(new OpenIdConnectMessage | |
{ | |
Error = "access_denied", | |
ErrorDescription = "The authorization grant has been denied by the resource owner", | |
RedirectUri = request.RedirectUri, | |
State = request.State | |
}); | |
return new HttpStatusCodeResult(200); | |
} | |
[HttpGet, Route("~/connect/logout")] | |
public async Task<ActionResult> Logout() | |
{ | |
// Note: when a fatal error occurs during the request processing, an OpenID Connect response | |
// is prematurely forged and added to the OWIN context by OpenIdConnectServerHandler. | |
// In this case, the OpenID Connect request is null and cannot be used. | |
// When the user agent can be safely redirected to the client application, | |
// OpenIdConnectServerHandler automatically handles the error and MVC is not invoked. | |
// You can safely remove this part and let Owin.Security.OpenIdConnect.Server automatically | |
// handle the unrecoverable errors by switching ApplicationCanDisplayErrors to false in Startup.cs | |
var response = OwinContext.GetOpenIdConnectResponse(); | |
if (response != null) | |
{ | |
return View("Error", response); | |
} | |
// When invoked, the logout endpoint might receive an unauthenticated request if the server cookie has expired. | |
// When the client application sends an id_token_hint parameter, the corresponding identity can be retrieved | |
// using AuthenticateAsync or using User when the authorization server is declared as AuthenticationMode.Active. | |
var identity = await OwinContext.Authentication.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationType); | |
// Extract the logout request from the OWIN environment. | |
var request = OwinContext.GetOpenIdConnectRequest(); | |
if (request == null) | |
{ | |
return View("Error", new OpenIdConnectMessage | |
{ | |
Error = "invalid_request", | |
ErrorDescription = "An internal error has occurred" | |
}); | |
} | |
return View("Logout", Tuple.Create(request, identity)); | |
} | |
[HttpPost, Route("~/connect/logout")] | |
[ValidateAntiForgeryToken] | |
public ActionResult Logout(CancellationToken cancellationToken) | |
{ | |
// Instruct the cookies middleware to delete the local cookie created | |
// when the user agent is redirected from the external identity provider | |
// after a successful authentication flow (e.g Google or Facebook). | |
OwinContext.Authentication.SignOut("ServerCookie"); | |
// This call will instruct Owin.Security.OpenIdConnect.Server to serialize | |
// the specified identity to build appropriate tokens (id_token and token). | |
// Note: you should always make sure the identities you return contain either | |
// a 'sub' or a 'ClaimTypes.NameIdentifier' claim. In this case, the returned | |
// identities always contain the name identifier returned by the external provider. | |
OwinContext.Authentication.SignOut(OpenIdConnectServerDefaults.AuthenticationType); | |
return new HttpStatusCodeResult(200); | |
} | |
[HttpPost, Route("~/connect/authorize/login"), ValidateAntiForgeryToken] | |
public ActionResult LoginWithPassword(LoginModel login, string queryString) | |
{ | |
Debug.Assert(!User.Identity.IsAuthenticated); | |
if (login.UserName == login.Password) | |
{ | |
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationType); | |
identity.AddClaim(new Claim(ClaimTypes.Name, login.UserName).WithDestination("id_token token")); | |
OwinContext.Authentication.SignIn(identity); | |
return new HttpStatusCodeResult(200); | |
} | |
return View("Login", new LoginViewModel(OwinContext.Authentication.GetExternalProviders(), "Wrong password")); | |
} | |
/// <summary> | |
/// Gets the IOwinContext instance associated with the current request. | |
/// </summary> | |
protected IOwinContext OwinContext | |
{ | |
get | |
{ | |
var context = HttpContext.GetOwinContext(); | |
if (context == null) | |
{ | |
throw new NotSupportedException("An OWIN context cannot be extracted from HttpContext"); | |
} | |
return context; | |
} | |
} | |
protected virtual async Task<Application> GetApplicationAsync(string identifier, CancellationToken cancellationToken) | |
{ | |
using (var context = new ApplicationContext()) | |
{ | |
// Retrieve the application details corresponding to the requested client_id. | |
return await (from application in context.Applications | |
where application.ApplicationID == identifier | |
select application).SingleOrDefaultAsync(cancellationToken); | |
} | |
} | |
} | |
<div class="jumbotron"> | |
<h2>Login</h2> | |
<h1>Authorization Server</h1> | |
@if (!string.IsNullOrEmpty(Model.Error)) | |
{ | |
<h1 style="color:red">@Model.Error</h1> | |
} | |
@{ | |
var action = string.Format("/connect/authorize/login?{0}", Request.QueryString); | |
} | |
<form action="@action" enctype="application/x-www-form-urlencoded" method="post"> | |
@Html.AntiForgeryToken() | |
<div class="form-horizontal"> | |
<h4>Forms Login</h4> | |
<hr /> | |
<input type="hidden" name="queryString" value="@Request.QueryString" /> | |
<div class="form-group"> | |
<div class="col-md-10"> | |
<label for="UserName">User name: </label> | |
<input type="text" id="UserName" name="username" /> | |
</div> | |
</div> | |
<div class="form-group"> | |
<div class="col-md-10"> | |
<label for="Password">Password: </label> | |
<input type="password" id="Password" name="password" /> | |
</div> | |
</div> | |
<div class="form-group"> | |
<div class="col-md-10"> | |
<label for="RememberMe">Remember Me</label> | |
<input type="checkbox" id="RememberMe" name="isPersistent" /> | |
</div> | |
</div> | |
<div class="form-group"> | |
<div class="col-md-offset-2 col-md-10"> | |
<input type="submit" value="Sign In" class="btn btn-default" /> | |
</div> | |
</div> | |
</div> | |
</form> | |
@foreach (var description in Model.Providers) | |
{ | |
<form action="/signin" method="post"> | |
<input type="hidden" name="Provider" value="@description.AuthenticationType" /> | |
<input type="hidden" name="ReturnUrl" value="@ViewBag.ReturnUrl" /> | |
<button class="btn btn-lg btn-success" type="submit">Connect using @description.Caption</button> | |
</form> | |
} | |
</div> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment