Securing a custom API controller with OAuth + Members?
Anyone got any experience of securing a custom API using OAuth and Umbraco members? I'm building a site that has members sign up, but I also then need their data to be accessible via a mobile app, so I want to create an API controller to communicate with and protect it using OAuth backed by the Umbraco Members system for the actual login credentials.
Hi matt have you seen the thread I had with John. We both had the idea of using identity server to do what you are describing. I posted some code which used the umbraco user manager as backing for the authentication.
I was using an authentication code flow with refresh tokens to provide long lived access but it also works with implicit flow if thats more appropriate.
Not sure which OAuth version you're looking for. When following the OAuth 2.0 specification, a user must specify an access token.
In the very small example below, my OAuthAttribute class will validate the access token, and find the member behind the access token. Given that this is just an example, the access token is simply validated against a small switch statement.
In a real scenario, you should obviously have some way of generating and storing the access tokens for each member, and possibly also log the member in for that particular request.
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
using Umbraco.Web.WebApi;
[OAuthAuthorizeAttribute]
public class OAuthApiController : UmbracoApiController {
// Just a "dummy" class that you should inherit from
}
public class OAuthAuthorizeAttribute : AuthorizeAttribute {
protected override bool IsAuthorized(HttpActionContext actionContext) {
string accessToken = HttpContext.Current.Request.QueryString["access_token"];
int memberId = 0;
switch (accessToken) {
case "let me in": memberId = 1096; break;
case "come on": memberId = 1097; break;
case "i have cookies": memberId = 1098; break;
}
return memberId > 0;
}
}
OAuth 1.0a involves a lot more security, but that should be possible as well.
For this to work you need a Token Server and a Token Authentication Handler.
The building blocks are there for you to copy in various projects but unfortunately it's not built (yet), this type of thing is planned for the RestApi project but of course we've all got way too busy with everything else happening ;)
To make this easier, you should:
Install UmbracoIdentity - https://github.com/Shazwazza/UmbracoIdentity/ so that you have an ASP.Net identity provider for your Umbraco members. Without this, it will be more difficult to plugin to all of the OWIN/Identity bits
Have a look at Identity Extensions: https://github.com/umbraco/UmbracoIdentityExtensions - this is for back office users, but you can use this code and tweak it for front-end members using the front-end members Identity provider from UmbracoIdentity
here you can find the code to create a Bearer Token server - to create tokens for you and - these tokens will contain your Identity information, claims, etc...
a Bearer Token Auth provider - to validate the tokens and create a Principle user for the request (AuthN )
You also have to take CORS into account with your token server endponit, again, Identity Extensions has this code built in as well
Using tokens should always be done behind SSL!
You will also have to take CORS into account with your controller endpoints (this is relatively simple with the WebApi Cors package)
Once you have AuthN in place, then AuthZ is normal, you don't have to do anything special there, you can authorize your endpoints however you want with roles, or specific claims, etc....
So I have managed to get this working based on the suggestions from Shan. Right now it's just a POC so not very friendly to share, but if I can tidy things up, I'll post on here.
Needless to say, if you do follow Shans guidelines, you should be able to get it working. It's just a steep learning curve if you don't know what you are doing (like me).
Can you please post your solution here. We are creating a mobile app which logs into CRM (contact) using custom login(username, password) or social media login. So planning to have something similar you are using.
I'd also like to to see any code.
I've got all the token server and token auth working but only for the back office on the end point /umbraco/oauth/token.
I can not get the backoffice to create a token if i change the token end point in UseUmbracoBackOfficeTokenAuth method so i am not sure how this gets invoked.
I've also copied the UseUmbracoBackOfficeTokenAuth and created a new method UseUmbracoMemberTokenAuth with a MemberAuthServerProvider which uses:
UmbracoMembersUserManager
I worked out the issue. Here is what I my MembersAuthServerprovider has ended up as
public class MembersAuthServerProvider : OAuthAuthorizationServerProvider
{
private readonly MembersAuthServerProviderOptions _options;
public MembersAuthServerProvider(MembersAuthServerProviderOptions options = null)
{
if (options == null)
options = new MembersAuthServerProviderOptions();
_options = options;
}
/// <summary>
/// Called at the final stage of a successful Token endpoint request. An application may implement this call in order to do any final
/// modification of the claims being used to issue access or refresh tokens. This call may also be used in order to add additional
/// response parameters to the Token endpoint's json response body.
/// </summary>
/// <param name="context">The context of the event carries information in and results out.</param>
/// <returns>
/// Task to enable asynchronous execution
/// </returns>
public override Task ValidateTokenRequest(OAuthValidateTokenRequestContext context)
{
ProcessCors(context);
return base.ValidateTokenRequest(context);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext
.GetUserManager<UmbracoMembersUserManager<UmbracoApplicationMember>>();
var user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await userManager.ClaimsIdentityFactory.CreateAsync(userManager, user, OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await userManager.ClaimsIdentityFactory.CreateAsync(userManager, user, CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName, 1).ToString());
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
public static AuthenticationProperties CreateProperties(string userName, string id)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", userName },
{ "id", id}
};
return new AuthenticationProperties(data);
}
private void ProcessCors(OAuthValidateTokenRequestContext context)
{
var accessControlRequestMethodHeaders = context.Request.Headers.GetCommaSeparatedValues(CorsConstants.AccessControlRequestMethod);
var originHeaders = context.Request.Headers.GetCommaSeparatedValues(CorsConstants.Origin);
var accessControlRequestHeaders = context.Request.Headers.GetCommaSeparatedValues(CorsConstants.AccessControlRequestMethod);
var corsRequest = new CorsRequestContext
{
Host = context.Request.Host.Value,
HttpMethod = context.Request.Method,
Origin = originHeaders == null ? null : originHeaders.FirstOrDefault(),
RequestUri = context.Request.Uri,
AccessControlRequestMethod = accessControlRequestMethodHeaders == null ? null : accessControlRequestMethodHeaders.FirstOrDefault()
};
if (accessControlRequestHeaders != null)
{
foreach (var header in context.Request.Headers.GetCommaSeparatedValues(CorsConstants.AccessControlRequestMethod))
{
corsRequest.AccessControlRequestHeaders.Add(header);
}
}
var engine = new CorsEngine();
if (corsRequest.IsPreflight)
{
try
{
// Make sure Access-Control-Request-Method is valid.
// ReSharper disable once UnusedVariable
var test = new HttpMethod(corsRequest.AccessControlRequestMethod);
}
catch (ArgumentException)
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
context.SetError("Access Control Request Method Cannot Be Null Or Empty");
//context.RequestCompleted();
return;
}
catch (FormatException)
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
context.SetError("Invalid Access Control Request Method");
//context.RequestCompleted();
return;
}
var result = engine.EvaluatePolicy(corsRequest, _options.CorsPolicy);
if (!result.IsValid)
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
context.SetError(string.Join(" | ", result.ErrorMessages));
//context.RequestCompleted();
return;
}
WriteCorsHeaders(result, context);
}
else
{
var result = engine.EvaluatePolicy(corsRequest, _options.CorsPolicy);
if (result.IsValid)
{
WriteCorsHeaders(result, context);
}
}
}
private void WriteCorsHeaders(CorsResult result, OAuthValidateTokenRequestContext context)
{
var headers = result.ToResponseHeaders();
if (headers != null)
{
foreach (var header in headers)
{
context.Response.Headers.Append(header.Key, header.Value);
}
}
}
}
I use the UmbracoIdentityStartup that is in the extensions package. If you follow that package, you have everything you need, Just need to modify the MemberAuthServerProvider as my code demonstrates
I have installed Umbraco Indentity 5.0 and I'm customizing the OAuth Umbraco package and it works server side and thank Identity it's possible to link the social login to Umbraco member. But where is stored the social login info? I think into a local cookie.
Now, I'm study how to do mobile app side to use social login and then pass the token to Umbraco OAuth to authorize and so.
Securing a custom API controller with OAuth + Members?
Anyone got any experience of securing a custom API using OAuth and Umbraco members? I'm building a site that has members sign up, but I also then need their data to be accessible via a mobile app, so I want to create an API controller to communicate with and protect it using OAuth backed by the Umbraco Members system for the actual login credentials.
I know there is the work Warren did with JWT http://creativewebspecialist.co.uk/2015/01/06/securing-umbraco-web-apis-using-json-web-tokens/ but I was wondering if anyone has done anything using OAuth and have any code they don't mind sharing?
Cheers
Matt
Hi matt have you seen the thread I had with John. We both had the idea of using identity server to do what you are describing. I posted some code which used the umbraco user manager as backing for the authentication.
See here if its any help to you https://our.umbraco.org/forum/developers/extending-umbraco/74636-use-identityserver-with-user-membership
I was using an authentication code flow with refresh tokens to provide long lived access but it also works with implicit flow if thats more appropriate.
Not sure which OAuth version you're looking for. When following the OAuth 2.0 specification, a user must specify an access token.
In the very small example below, my
OAuthAttribute
class will validate the access token, and find the member behind the access token. Given that this is just an example, the access token is simply validated against a small switch statement.In a real scenario, you should obviously have some way of generating and storing the access tokens for each member, and possibly also log the member in for that particular request.
OAuth 1.0a involves a lot more security, but that should be possible as well.
Hi Matt,
For this to work you need a Token Server and a Token Authentication Handler.
The building blocks are there for you to copy in various projects but unfortunately it's not built (yet), this type of thing is planned for the RestApi project but of course we've all got way too busy with everything else happening ;)
To make this easier, you should:
Hi, will this method work for Umbraco v11? If not what is the method for v11.
So I have managed to get this working based on the suggestions from Shan. Right now it's just a POC so not very friendly to share, but if I can tidy things up, I'll post on here.
Needless to say, if you do follow Shans guidelines, you should be able to get it working. It's just a steep learning curve if you don't know what you are doing (like me).
Hi Matt,
Can you please post your solution here. We are creating a mobile app which logs into CRM (contact) using custom login(username, password) or social media login. So planning to have something similar you are using.
Any tip and code will be really helpfu.
Cheers, Pramod
I'd also like to to see any code. I've got all the token server and token auth working but only for the back office on the end point /umbraco/oauth/token.
I can not get the backoffice to create a token if i change the token end point in UseUmbracoBackOfficeTokenAuth method so i am not sure how this gets invoked.
I've also copied the UseUmbracoBackOfficeTokenAuth and created a new method UseUmbracoMemberTokenAuth with a MemberAuthServerProvider which uses: UmbracoMembersUserManager
So any pointers would be much appreciated.
I should add this is version 7.5.2
cheers,
mark
I would be keen on some sample code as well. I have followed Shan's instructions. But I am getting a error in the AuthServerprovider.
I changed BackOfficeAuthServerProvider in the GrantResourceOwnerCredentials to
But it is returning a null. as userManager.
I worked out the issue. Here is what I my MembersAuthServerprovider has ended up as
Hi, I've to do the same thing. Do it work?
So, you installed:
Here is a JWT implementation : https://creativewebspecialist.co.uk/2015/01/06/securing-umbraco-web-apis-using-json-web-tokens/
Yes, I installed UmbracoIdenity and UmbracoIndentityExtensions.
For the JWT token, I just created a formatter , based on this tutorial http://bitoftech.net/2014/10/27/json-web-token-asp-net-web-api-2-jwt-owin-authorization-server/
I see your code MembersAuthServerprovider. Have you putted into OWIN startup? Have you tried to use with social login?
I use the UmbracoIdentityStartup that is in the extensions package. If you follow that package, you have everything you need, Just need to modify the MemberAuthServerProvider as my code demonstrates
I have installed Umbraco Indentity 5.0 and I'm customizing the OAuth Umbraco package and it works server side and thank Identity it's possible to link the social login to Umbraco member. But where is stored the social login info? I think into a local cookie.
Now, I'm study how to do mobile app side to use social login and then pass the token to Umbraco OAuth to authorize and so.
It creates a table in the main Umbraco db.
Do you mean UmbracoExternalLogin? Here is the record saved. Is't "providerKey" the autheticate Google token?
is working on a reply...