Refresh token doesn't fail after deleting the user - asp.net-mvc

I'd like to know if it's my failure or the bug/feature of ASP.NET Identity.
We use ASP.NET Identity 1.0 in our ASP.NET MVC 5 project. OAuth is configured like this:
public partial class Startup
{
static Startup()
{
PublicClientId = "self";
UserManagerFactory = () => new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
RefreshTokenProvider = new AuthenticationTokenProvider()
{
OnCreate = CreateRefreshToken,
OnReceive = ReceiveRefreshToken
},
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static Func<SphUserManager> UserManagerFactory { get; set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/login")
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
private static void CreateRefreshToken(AuthenticationTokenCreateContext context)
{
context.SetToken(context.SerializeTicket());
}
private static void ReceiveRefreshToken(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
}
We use Web API to register and login the user. Refresh token is than used to refresh access token. This is what we didn't expect:
Register the user
Login the user and obtain access token and refresh token (/token, grant_type=password...)
Delete the user (directly from database or in administration).
Call refresh token and the request will not fail. The access token is prolonged and the user is still authenticated (/token, grant_type=refresh_token...)
Is it correct behavior? Should I do something special to "invalidate" tokens?

Refresh token supports in the Katana OAuth2 middleware is essentially up to you, so if you delete the user then it's up to that logic to also revoke (delete) all refresh tokens for that user.

Related

How to setup MVC requests to use Oauth 2.0 access token?

I am working on an Asp.net Web API + MVC project. While creating the project, I selected 'Individual User Accounts' option for authentication. After login, the provider issues access token and I use this token in HTTP header of ajax calls and it works fine with Ajax calls. I also have MVC actions which are used to navigate to different pages. These actions are protected using [Authorize] attribute of System.web.MVC. While navigating to these actions authorization fails and I am redirected to login page. How do I configure Oauth 2.0 access token to be used in non ajax requests (MVC requests) also.
Startup.Auth.cs:
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Home/Login"),
LogoutPath = new PathString("/Account/LogOff"),
ExpireTimeSpan = TimeSpan.FromHours(1.0),
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true // dont use this for production,
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
WebApiConfig.cs:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
ApplicationOAuthProvider.cs:
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
public ApplicationOAuthProvider(string publicClientId)
{
if (publicClientId == null)
{
throw new ArgumentNullException("publicClientId");
}
_publicClientId = publicClientId;
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser 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 user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
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 override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
{
context.Validated();
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
Uri expectedRootUri = new Uri(context.Request.Uri, "/");
if (expectedRootUri.AbsoluteUri == context.RedirectUri)
{
context.Validated();
}
}
return Task.FromResult<object>(null);
}
public static AuthenticationProperties CreateProperties(string userName)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", userName }
};
return new AuthenticationProperties(data);
}
}
ASP.Net MVC is a server side web app technology and [Authorize] actions expect to receive a cookie rather than checking for a token.
Not sure if this is overridable and in this type of solution you often end up having to deal with an ugly mix of cookies / tokens / server side code / client side code.
A more modern / cleaner separation is to develop a single page app + REST API instead. Code tends to be simpler and it feels like a better fit for the requirements you mention.
To see what I mean, maybe have a quick browse of the UI code in this sample:
https://github.com/gary-archer/oauth.websample1
The repo links to some blog posts if it's an option you're interested in.

How i can store bearer tokens on server-side and after validate how to delete on logout in Web API 2?

I am creating web api project, by default it have account controller in which i found Register,Logout and other api's .
Using Web API 2, OAuth and OWIN
By /token i generated bearer token and his expiry time which is storing in OWIN Cookie authentication.
My Question is : -
how i can delete this token when user logout because after using logout service i can still call list data which is decorated with [Authorize]
can i store it in database and validate it, delete it when user logout
Logout code is below
// POST api/Account/Logout
[Route("Logout")]
public IHttpActionResult Logout()
{
// Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
return ok();
}
and my /token code is below
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
}
You cannot delete the token in server, however you can forget the token in client side.
Or you can create refresh token service
Just create the class
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider {
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public async Task CreateAsync(AuthenticationTokenCreateContext context) {
var guid = Guid.NewGuid().ToString();
_refreshTokens.TryAdd(guid, context.Ticket);
context.SetToken(guid);
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context) {
AuthenticationTicket ticket;
if (_refreshTokens.TryRemove(context.Token, out ticket)) {
context.SetTicket(ticket);
}
}
}
Register it in
static Startup() {
OAuthOptions = new OAuthAuthorizationServerOptions {
TokenEndpointPath = new PathString("/api/Login"),
Provider = new OAuthProvider(),
RefreshTokenProvider = new SimpleRefreshTokenProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),
AllowInsecureHttp = true,
};
}
Override OAuthAuthorizationServerProvider
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) {
if (context.TryGetBasicCredentials(out clientId, out clientSecret)) {
if (clientSecret == "secret") {
context.OwinContext.Set<string>("as:client_id", clientId);
context.Validated();
}
}
return Task.FromResult<object>(null);
}
and your service request should be look like this
Authorization: Basic Y2xpZW50MTpzZWNyZXQ=
Content-Type: application/x-www-form-urlencoded
username=care%40agentExperience.com&password=test&client_id=client1&clientSecret=secret&grant_type=refresh_token

How can I get OwinContext from SignalR Hub?

I have an ASP.NET MVC 5 app that is written on the top of ASP.NET MVC 5 framework using c#.
In this app, I am using SignalR to create a WebSocket connection between my app and the user's browser.
In my SignalR hub, I want to be able to access the OwinContext object.
How can I access the OwinContext object from my hub?
This is what I tried
Context.Request.GetHttpContext().GetOwinContext();
However, this is giving me the following error
No owin.Environment item was found in the context.
I added the <add key="owin:AutomaticAppStartup" value="true" /> to my Web.Config file.
This is how Startup class look like
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;
using System;
[assembly: OwinStartup(typeof(TestProject.App_Start.Startup))]
namespace TestProject.App_Start
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
ConfigureAuth(app);
// Any connection or hub wire up and configuration should go here
app.MapSignalR();
}
public void ConfigureAuth(IAppBuilder app)
{
// need to add UserManager into owin, because this is used in cookie invalidation
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = Settings.ApplicationCookie,
LoginPath = new PathString("/Login"),
Provider = new CookieAuthenticationProvider(),
CookieName = Settings.ApplicationCookieName,
CookiePath = Settings.CookiePath,
CookieHttpOnly = true,
SlidingExpiration = true,
ExpireTimeSpan = TimeSpan.FromHours(24),
});
}
}
}
UPDATED Case use explanation
In some case I need to update the user claims from inside the Hub. This requires to inform the Authentication Manager about the new claims. So I use the following method to update the ClaimsIdentity
private static void InformAuthManager(ClaimsIdentity identity, HttpContextBase context = null)
{
IAuthenticationManager authenticationManager;
if (context != null)
{
authenticationManager = context.GetOwinContext().Authentication;
}
else
{
authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
}
var claims = new ClaimsPrincipal(identity);
var authProperties = new AuthenticationProperties() { IsPersistent = true };
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(claims, authProperties);
}

OAuth access and refresh token control / management on user password change

We are in the process of developing a in house mobile application and web api.
We are using asp.net web api 2 with asp.net Identy 2 OAuth.
I have got the api up and running and giving me a bearer token. However I want to slightly modify the process flow to something like along the lines of this:
App user logs in to api with username and password.
App receives Refresh-token which is valid for 30 days.
App then requests an access token providing the api with the refresh token. ( Here I want to be able to invalidate a request if the user has changed their password or their account has been locked).
App Gets An Access token which is valid for 30 minutes or it gets a 401 if the password check failed.
The App can access the api with the given access token for the next 29 minutes. After that the app will have to get a new access token with the refresh token.
Reason I want to do this is in order to stop a users devices gaining access to the api after they have changed their password. If their phone gets stolen they need to be able to login to the website and change their password so the new owner of the phone cannot gain access to our companies services.
Is my proposed solution do able, and if so is it a sensible solution? I haven't forgotten any crucial elements?
I am willing to do a db access on ever token refresh, but not on every API call.
To summarize my questions are:
Is my planned method sensible?
How would I securely check if the password has changed or if account is locked in the refresh token process.
Please find my current OAuth Setups and classes below: (I have tried to add the refresh token functionality, but have not attempted to add any password verification yet)
Startup.Auth.cs
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(IdentityDbContext.Create);
app.CreatePerOwinContext<FskUserManager>(FskUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
RefreshTokenProvider = new ApplicationRefreshTokenProvider(),
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
ApplicationRefreshTokenProvider.cs
public class ApplicationRefreshTokenProvider : AuthenticationTokenProvider
{
public override void Create(AuthenticationTokenCreateContext context)
{
// Expiration time in minutes
int refreshTokenExpiration = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["ApiRefreshTokenExpiry"]);
context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddMinutes(refreshTokenExpiration));
context.SetToken(context.SerializeTicket());
}
public override void Receive(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
}
ApplicationOAuthProvider.cs
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
public ApplicationOAuthProvider(string publicClientId)
{
if (publicClientId == null)
{
throw new ArgumentNullException("publicClientId");
}
_publicClientId = publicClientId;
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<FskUserManager>();
FskUser 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 user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
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 override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
{
context.Validated();
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
Uri expectedRootUri = new Uri(context.Request.Uri, "/");
if (expectedRootUri.AbsoluteUri == context.RedirectUri)
{
context.Validated();
}
}
return Task.FromResult<object>(null);
}
public static AuthenticationProperties CreateProperties(string userName)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", userName }
};
return new AuthenticationProperties(data);
}
}
If I understood your task right, here is an idea.
On the create access token event, you can check if the password has been changed from the website and if so, revoke the refresh token. (you can create some flag that the password has been changed or something)
It should not be often when you are creating an access token so there should be no problems with the db access.
Now the question is how to revoke a refresh token. Unless there is a build in way you will have to implement a custom one. An idea here is to check the refresh token creation date and the date of the change password operation. If the change password operation is done after the creation of the refresh token, you do not authenticate the user.
Let me know what you think of this.

Bearer Token Authentication with ASP.net API

I am looking into using an ASP.net web API to set up request authentication with a bearer token. When you use the OWIN server middle-ware, where is the encryption key coming from? How would the server revoke a token that has not expired?
OWIN ServerMiddleware's default Tiken data protection approach is using DPAPI (Data Protection API)
For revoking tokens at the server side, Token Store need to be implemented. You can use AccessTokenProvider.Create to create and store Token.
Here is an example for such scenario. Take this as an example code snippets.
Register in Startup.cs
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
AuthorizeEndpointPath = new PathString("/Authorize"),
TokenEndpointPath = new PathString("/Token"),
ApplicationCanDisplayErrors = true,
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizationCodeProvider = new MyAuthenticationTokenProvider(TokenType.Code),
AccessTokenProvider = new MyAuthenticationTokenProvider(TokenType.Access),
RefreshTokenProvider = new MyAuthenticationTokenProvider(TokenType.Refresh),
AuthorizationCodeFormat = new MyFormatProvider("MyAudiences"),
AccessTokenFormat = new MyFormatProvider("MyAudiences"),
RefreshTokenFormat = new MyFormatProvider("MyAudiences"))
});
}
Provide Encryption: This is based on the JwtFormat in the Katana project. The JwtFormat.protect() method is still not supported. So you need to create your own implementation.
//You need to manage your Key in this class
public class MyFormatProvider: ISecureDataFormat<AuthenticationTicket>
{
public MyFormatProvider(string allowedAudiences)
{
}
public string Protect(AuthenticationTicket data)
{
return "encrypted";
}
public AuthenticationTicket Unprotect(string protectedText)
{
return new AuthenticationTicket(new System.Security.Claims.ClaimsIdentity(), new AuthenticationProperties());
}
}
Token Provider
public enum TokenType { Code,Access,Refresh }
public class MyAuthenticationTokenProvider : AuthenticationTokenProvider
{
TokenType tokenType = TokenType.Access;
public MyAuthenticationTokenProvider(TokenType tokenType)
{
}
public override void Create(AuthenticationTokenCreateContext context)
{
/*Create Token, Store Token and Tiket info*/
context.SetToken("MyToken");/*This will call Your MyFormatProvider internally*/
base.Create(context);
}
public override void Receive(AuthenticationTokenReceiveContext context)
{
/*retrieve Token and Tiket info to process*/
base.Receive(context);
}
}

Resources