FormsAuthentication with MVC5 - asp.net-mvc

In MVC5 ASP.Identity replaces old form authentication. However as per the discussion here A type of FormsAuthentication still exists though. According to Microsoft,
But i also found Microsoft.Owin.Security.Forms library is also deprecated (check this nuget link)
What are my options here if i want to use ASP.NET MVC5 and i want to store userid & password in SQL table ( eg aspnet_users & aspnet_membership SQL tables)
( this should be a quick temporary solution until we move to new OpenIdConnect)

ASP.NET Identity does support cookie based authentication out of the box, allowing you to store logins in DB and having a "forms authentication like" mechanism. Default tables schema are not the same than with membership, but it is customizable.
Bootstrapping sample:
[assembly: OwinStartup(typeof(YourNamespace.Startup))]
namespace YourNamespace
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var options = GetCookieOptions();
app.UseCookieAuthentication(options);
}
public static CookieAuthenticationOptions GetCookieOptions()
{
var options = new CookieAuthenticationOptions
{
AuthenticationType =
DefaultAuthenticationTypes.ApplicationCookie,
SlidingExpiration = true,
// On ajax calls, better have a 401 rather than a redirect
// to an HTML login page.
// Taken from http://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = ctx =>
{
if (!IsAjaxRequest(ctx.Request))
{
// Patching by the way the absolute uri using http
// instead of https, when we are behind a lb
// terminating the https: returning only
// PathAndQuery
ctx.Response.Redirect(new Uri(ctx.RedirectUri)
.PathAndQuery);
}
}
}
};
if (!string.IsNullOrEmpty(Settings.Default.LoginPath))
options.LoginPath = new PathString(Settings.Default.LoginPath);
if (!string.IsNullOrEmpty(Settings.Default.AuthCookieName))
options.CookieName = Settings.Default.AuthCookieName;
if (!string.IsNullOrEmpty(Settings.Default.AuthCookieDomain))
options.CookieDomain = Settings.Default.AuthCookieDomain;
if (Settings.Default.ForceSecuredCookie)
options.CookieSecure = CookieSecureOption.Always;
return options;
}
// Taken from http://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/
private static bool IsAjaxRequest(IOwinRequest request)
{
var query = request.Query;
if (query != null && StringComparer.OrdinalIgnoreCase.Equals(
query["X-Requested-With"], "XMLHttpRequest"))
return true;
var headers = request.Headers;
return headers != null && StringComparer.OrdinalIgnoreCase.Equals(
headers["X-Requested-With"], "XMLHttpRequest");
}
}
}
(Settings.Default. are custom configuration properties of the project in those sample.)
sign-in, sign-out sample:
UserManager<IdentityUser> yourUserManager;
public bool SignIn(string login, string password, bool rememberMe)
{
var user = yourUserManager.Find(userName, password);
if (user == null)
return false;
var expiration = rememberMe ?
Settings.Default.PermanentAuthCookieExpiration :
Settings.Default.AuthCookieExpiration;
var authenticationManager =
HttpContext.Current.GetOwinContext().Authentication;
var claimsIdentity = yourUserManager.CreateIdentity(user,
DefaultAuthenticationTypes.ApplicationCookie);
authenticationManager.SignIn(
new AuthenticationProperties
{
AllowRefresh = true,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(expiration),
IsPersistent = rememberMe
}, claimsIdentity);
return true;
}
public void IIdentityUserManager.SignOut()
{
var authenticationManager =
HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.SignOut();
}
And of course, with MVC, use AuthorizeAttribute as a global filter along with [AllowAnonymous] on actions which do not require authorization.

Related

OpenID Connect: How to add custom claims data in the client credential flow

I'm setting up a client credential flow with my identity server to get an access token from a client. I'm able to get the access token with the following code,
Identity server configuration:
public void Configuration(IAppBuilder app)
{
app.Map("/identity", idsrvApp =>
{
var corsPolicyService = new DefaultCorsPolicyService()
{
AllowAll = true
};
var idServerServiceFactory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get())
.UseInMemoryUsers(Users.Get());
var options = new IdentityServerOptions
{
Factory = idServerServiceFactory,
SiteName = "Demo",
IssuerUri = IdentityConstants.IssuerUri,
PublicOrigin = IdentityConstants.STSOrigin,
SigningCertificate = LoadCertificate()
};
idsrvApp.UseIdentityServer(options);
});
}
Identity Server - Client configuration:
public static class Clients
{
public static IEnumerable<Client> Get()
{
return new[]
{
new Client
{
ClientId = "ClientSDK",
ClientName = "Client SDK (Client Credentials)",
Flow = Flows.ClientCredentials,
AllowAccessToAllScopes = true,
ClientSecrets = new List<Secret>()
{
new Secret(IdentityConstants.ClientSecret.Sha256())
}
}
};
}
}
MVC Client:
var oAuth2Client = new TokenClient(
IdentityConstants.STSTokenEndpoint,
"ClientSDK",
IdentityConstants.ClientSecret);
var tokenResponse = oAuth2Client.RequestClientCredentialsAsync("MyScope").Result;
return tokenResponse.AccessToken;
I'm able to get the access token(i.e. JWT). Can one please tell me how to add a unique key like (UserId) from my database, when the JWT is created with its claims data when the token is created.
First, you need to create custom attribute "userId" on Azure Portal, and apply it for selected application. Then follow this example,
Update user using Graph API
If you are using built in user flows, then you need to select "userId" for your application.
If you are using custom policy, then following process.
JWT token shows only output claims of Azure AD B2C custom policy. It is a multi steps process to create and update custom policy. Here is link to read more about How to create custom attribute
You should implement custom user store for validating user and adding claims from database. Change startup code like below, Userrepository class represents database communication to authenticate user and get claims from database:
var idServerServiceFactory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get())
.AddCustomUserStore();
Add below classes and change according to your requirement:
public static class CustomIdentityServerBuilderExtensions
{
public static IIdentityServerBuilder AddCustomUserStore(this IIdentityServerBuilder builder)
{
builder.AddProfileService<UserProfileService>();
builder.AddResourceOwnerValidator<UserResourceOwnerPasswordValidator>();
return builder;
}
}
public class UserProfileService : IProfileService
{
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
UserRepository userRepository=new UserRepository();
var user = userRepository.GetUserById(int.Parse(context.Subject.GetSubjectId()));
if (user != null)
{
var userTokenModel = _mapper.Map<UserTokenModel>(user);
var claims = new List<Claim>();
claims.Add(new Claim("UserId", user.UserId));
// Add another claims here
context.IssuedClaims.AddRange(claims);
}
public async Task IsActiveAsync(IsActiveContext context)
{
}
}
public class UserResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
UserRepository userRepository=new UserRepository();
var userLoginStatus = userRepository.GetUserById(context.UserName, context.Password);
if (userLoginStatus != null)
{
context.Result = new GrantValidationResult(userLoginStatus.UserId.ToString(),
OidcConstants.AuthenticationMethods.Password);
}
else
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidClient,
"Wrong Credentials");
}
}
}

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.

Azure AD Auto signing out after signin

So ive gone through as many posts as I can, and I cant seem to sort this out!
My client wants us to allow logging into their ADFS via an MVC platform that I have built, so I am trying to allow them to sign into their Azure AD to sign into the platform.
When I am redirected to my signin page for Azure AD(MS Login), I type in my credentials and then it looks like it is doing a quick redirect loop and then automatically signs me out, I am going crazy!!!
Below is everything I have setup:
On Azure AD:
Created App service and put ApplicationId and TenantId in my Web.config
<add key="ida:ClientId" value="ApplicationID from AzureAD" />
<add key="ida:Tenant" value="TenantId from AzureAD" />
<add key="ida:AADInstance" value="https://login.microsoftonline.com/{0}" />
<add key="ida:RedirectUri" value="https://sitename.azurewebsites.net/Home/Index" />
<add key="ida:PostLogoutRedirectUri" value="https://sitename.azurewebsites.net" />
On Startup.Auth.cs
public partial class Startup
{
// Calling the keys values from Web.config file
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
// Concatenate aadInstance, tenant to form authority value
private string authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
// ConfigureAuth method
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
//app.UseCookieAuthentication(new CookieAuthenticationOptions());
//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
//Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = (context) =>
{
context.HandleResponse();
context.OwinContext.Response.Redirect("/Home/Index");
return Task.FromResult(0);
}
}
});
} // end - ConfigureAuth method
On my routeConfig : This was done so that my custom landing page can be loaded first, on this page is a button saying "Enter platform", which the client will click on and go to Azure AD signin(MS Login page)
public static class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.LowercaseUrls = true;
routes.MapRoute("Default", "{controller}/{action}/{id}", new
{
controller = "Account",
action = "Login",
id = UrlParameter.Optional
}).RouteHandler = new DashRouteHandler();
}
}
Account Controller
[Authorize]
public void SignIn()
{
clsHomeScreen clsHomeScreen = new clsHomeScreen();
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
Response.Redirect("/");
}
[AllowAnonymous]
[OutputCache(NoStore = true, Location = OutputCacheLocation.None)]
public ActionResult Login(string returnUrl)
{
// We do not want to use any existing identity information
EnsureLoggedOut();
// Store the originating URL so we can attach it to a form field
var viewModel = new AccountLoginModel { ReturnUrl = returnUrl };
return View(viewModel);
}
HomeController - This is where it SHOULD be redirected to after signing in, but its not:
[Authorize]
public async Task<ActionResult> Index()
{
HomeScreenLists HS = new HomeScreenLists();
IEnumerable<Challenges> ActiveChallenges;
IEnumerable<Challenges> PrivateChallenges;
string loggedInUserId = "";
string loggedInEmail = "";
var userClaims = User.Identity as System.Security.Claims.ClaimsIdentity;
string email = userClaims?.FindFirst(System.IdentityModel.Claims.ClaimTypes.Name)?.Value;
string firstname = userClaims?.FindFirst(System.IdentityModel.Claims.ClaimTypes.GivenName)?.Value;
string lastname = userClaims?.FindFirst(System.IdentityModel.Claims.ClaimTypes.Surname)?.Value;
string userId = "";
//The Email will not contain an #(i.e. an email address) if not using Azure AD to sign in.
if (!email.Contains("#"))
{
loggedInUserId = User.Identity.GetUserId();
goto LoggedInUser_Found;
}
if (TempData["LoggedInEmail"] != null)
{
if (email != TempData["LoggedInEmail"].ToString())
{
userId = clsHomeScreen.GetUserId(TempData["LoggedInEmail"].ToString());
}
else
{
userId = clsHomeScreen.GetUserId(email);
}
}
if (email != null)
{
userId = clsHomeScreen.GetUserId(email);
}
if (userId == null || userId == "")
{
clsUsers clsUsers = new clsUsers();
if (TempData["LoggedInEmail"] != null)
{
loggedInEmail = TempData["LoggedInEmail"].ToString();
var userDetails = clsUsers.GetUsers().Where(x => x.Email == loggedInEmail).FirstOrDefault();
loggedInUserId = userDetails.Id;
}
else
{
if(userId == null)
{
await RegisterAAD();
userId = clsHomeScreen.GetUserId(email);
loggedInUserId = userId;
}
else
{
loggedInUserId = User.Identity.GetUserId();
}
}
}
else
{
loggedInUserId = userId;
}
LoggedInUser_Found:
int iBU = (int)db.Users.FirstOrDefault(x => x.Id == loggedInUserId).fkiBusinessUnitId;
if (iBU == 0)
{
HS.HasBU = false;
TempData["HasBU"] = "No";
TempData["UserId"] = loggedInUserId;
}
else
{
HS.HasBU = true;
TempData["HasBU"] = "Yes";
TempData["UserId"] = loggedInUserId;
}
bool isAdmin = false;
if (User.IsInRole("Administrator"))
{
isAdmin = true;
}
ActiveChallenges = clsChallenges.GetActiveChallenges();
PrivateChallenges = clsChallenges.GetPrivateChallenges(loggedInUserId, isAdmin);
HS.HomeScreenList = clsHomeScreen.GetHomeScreenAdverts();
HS.ActiveChallengesList = ActiveChallenges;
HS.PrivateChallengesList = PrivateChallenges;
HS.UserId = loggedInUserId;
return View(HS);
}
So if I remove the [Authorize] attribute on the Index ActionResult, then it does a continuous redirect loop.
What ive tried:
I have tried using the KentorCookiSaver, which didnt work.
Recreating the app service
Changed the redirectUrl in Azure AD App Registration
Someone even spoke about rewriting the cookies, which I tried, but dont know if I followed the steps correctly, the link is Here
Ive tried so many things that I cant even remember what ive tried. Could anyone possibly help with what I am doing wrong, please.
Thanks a million!
So with some help from someone who knows these things, my problem was solved.
Ultimately what it came down to was:
I needed to add the RedirectUri into my Web.config and into my Startup.Auth
Web.Config
<add key="ida:RedirectUri" value="https://sitename.azurewebsites.net/Home/Index"/>
Startup.Auth
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = (context) =>
{
context.HandleResponse();
context.OwinContext.Response.Redirect("/Home/Index");
return Task.FromResult(0);
}
}
});
My Signin process kept routing back to my Account/Login page when failing, when it should have routed to my Home/Index, as I was using my Account/Login as my landing page and authentication only happening there after, the problem that happened here was because I did an "EnsureLogOut" on the Account/Login, thus why it kept logging me out first before wanting to authenticate. So instead of the Redirect = "/" I changed as follows:
public void SignIn()
`{`
`clsHomeScreen clsHomeScreen = new clsHomeScreen();`
`if (!Request.IsAuthenticated)`
`{`
`HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/Home/Index" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);`
`}`
`Response.Redirect("/Home/Index");`
`}`
Perhaps this is something that might not help others, but maybe it helps them in the right direction.

How to prevent an ASP NET MVC application requesting authorization from Google every hour?

We are using packages Google.Apis.Calendar.v3 and Google.Apis.Auth.Mvc in an ASP .NET MVC application with users.
We want each user to have access to their Google Calendar, so it is necessary that each one of them give permission to our application. We have read a lot and it is said that there is a way to prevent the application from asking the user for these credentials every hour
We have looked at solutions like this and do not work for us
solutions like this one we do not know how to use them in our MVC application
users may or may not register on our site using google
This is the last code that we tried and everything works correctly, except that every hour the users who had already given permission to our application had to do it again and to re-authenticate with google and give permissions:
FlowMetadata implementation
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow =
new ForceOfflineGoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "MY_VALID_CLIENT_ID",
ClientSecret = "MY_VALID_CLIENT_SECRET"
},
Scopes = new[] { CalendarService.Scope.Calendar },
DataStore = new FileDataStore("Google.Api.Auth.Store")
});
public override string GetUserId(Controller controller)
{
var userId = controller.User.Identity.GetUserId();
return userId;
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
}
GoogleAuthorizationCodeFlow implementation forcing offline
internal class ForceOfflineGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
{
public ForceOfflineGoogleAuthorizationCodeFlow(GoogleAuthorizationCodeFlow.Initializer initializer) : base(initializer) { }
public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri)
{
return new GoogleAuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl))
{
ClientId = ClientSecrets.ClientId,
Scope = string.Join(" ", Scopes),
RedirectUri = redirectUri,
AccessType = "offline",
ApprovalPrompt = "force"
};
}
}
AuthCallbackController
public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
{
protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
{
get { return new AppFlowMetadata(); }
}
}
Test controller action
public async Task IndexAsync(CancellationToken cancellationToken)
{
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
AuthorizeAsync(cancellationToken);
if (result.Credential != null)
{
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = result.Credential,
ApplicationName = "TEST",
});
IList list = service.CalendarList.List().Execute().Items;
var selected = list.First();
EventsResource.ListRequest request = service.Events.List(selected.Id);
request.TimeMin = new DateTime?(new DateTime(2017, 1, 1));
request.ShowDeleted = false;
request.SingleEvents = true;
request.MaxResults = 10;
request.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime;
Events events = request.Execute();
return View(events);
}
else
{
return new RedirectResult(result.RedirectUri);
}
}

Web Api with Owin with JWT always fails to authorize request

I have followed the tutorials up till this point in the series. I am using one project in the solution that acts as both the token issuing authority as well as the resource server.
The JWT is generated using the endpoint mentioned in the startup class and I validated it on jwt.io as well. However when I pass this JWT using Postman on Chrome to the resource API end point secured with an Authorize attribute, I always find it returning
{
"message": "Authorization has been denied for this request." }
The other api method as in the api controller class below works when called thru Postman on Chrome.
I have used the latest versions of all dlls required from the nuget console
Code in the startup class
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(ConfigurationManager.AppSettings["Issuer"]),
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
string issuer = ConfigurationManager.AppSettings["Issuer"];
string audienceId = ConfigurationManager.AppSettings["AudienceId"];
byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["AudienceSecret"]);
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceId },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
});
}
Code in the Custom OAuthProvider
public class CustomOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
public override Task MatchEndpoint(OAuthMatchEndpointContext context)
{
//avoid pre-flight calls
if (context.OwinContext.Request.Method == "OPTIONS" && context.IsTokenEndpoint)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Methods", new[] { "POST" });
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "accept", "authorization", "content-type" });
context.OwinContext.Response.StatusCode = 200;
context.RequestCompleted();
return Task.FromResult<object>(null);
}
return base.MatchEndpoint(context);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
//setting up claims in the constructor of class UserDetails
UserDetails user = new UserDetails();
user.UserName = context.UserName;
user.FirstName = "Dummy First";
user.LastName = "Dummy Last";
ClaimsIdentity identity = new ClaimsIdentity("JWT-BearerAuth-Test");
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
foreach (string claim in user.Claims)
{
identity.AddClaim(new Claim(ClaimTypes.Role, claim));
}
var ticket = new AuthenticationTicket(identity, null);
context.Validated(ticket);
}
}
The custom JWT class
public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string _issuer = string.Empty;
public CustomJwtFormat(string issuer)
{
_issuer = issuer;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string audienceId = ConfigurationManager.AppSettings["AudienceId"];
string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["AudienceSecret"];
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
}
The Resource server's Api controller
public class AdminController : ApiController
{
//This call works
public IHttpActionResult ReadData(string id)
{
return Ok("ID sent in:" + id);
}
//[Authorize(Roles="EditRecord")] //doesnt work
[Authorize] //doesnt work either
public IHttpActionResult EditData(string id)
{
return Ok("Edited ID:" + id);
}
}
My environment is VS2013 with Framework 4.5 using OAuth2 with Web Api 2. Please excuse the long post.
You need to make sure that values for issuer, audienceId, and audienceSecret used in method "ConfigureOAuthTokenConsumption" are the same values used when you generated the JWT token, take care of trailing slashes "/".
This is the only thing comes to my mind right now.

Resources