I'm using Owin and ASP.NET Identity to use OAuth tokens for securing my Web API methods. The token subsystem is set up as such:
var oauthOptions = new OAuthAuthorizationServerOptions()
{
TokenEndpointPath = new PathString("/Token"),
Provider = new SimpleAuthorizationServerProvider(),
AccessTokenFormat = new TicketDataFormat(app.CreateDataProtector(typeof(OAuthAuthorizationServerMiddleware).Namespace, "Access_Token", "v1")),
RefreshTokenFormat = new TicketDataFormat(app.CreateDataProtector(typeof(OAuthAuthorizationServerMiddleware).Namespace, "Refresh_Token", "v1")),
AccessTokenProvider = new AuthenticationTokenProvider(),
RefreshTokenProvider = new AuthenticationTokenProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
app.UseOAuthAuthorizationServer(oauthOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
It works great for requesting tokens based on username/password and then consuming those tokens. However, since the user is already authenticated when hitting the controller that renders the SPA, I would like to generate the token in my view and pass it on to the Javascript code, instead of having to log in again in the SPA.
So my question is: how do I manually generate my token so I can include it in my SPA view?
You can generate access token inside a controller by calling OAuthBearerOptions.AccessTokenFormat.Protect(ticket) and the code will look as the below:
private JObject GenerateLocalAccessTokenResponse(string userName)
{
var tokenExpiration = TimeSpan.FromDays(1);
ClaimsIdentity identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
var props = new AuthenticationProperties()
{
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.Add(tokenExpiration),
};
var ticket = new AuthenticationTicket(identity, props);
var accessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
JObject tokenResponse = new JObject(
new JProperty("userName", userName),
new JProperty("access_token", accessToken),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", tokenExpiration.TotalSeconds.ToString()),
new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())
);
return tokenResponse;
}
And you need to declare you OAuthBearerOptions as static property in class Startup.cs
But if you are looking to implement silent refresh for access token without requesting the user to login again, then you should consider implementing refresh token grant, do not do it like the way you suggested. You can read my detailed blog post on how to generate refresh tokens in SPA built with AngularJS.
Hope this answers your question.
Related
I have implemented FirebaseAuth in my .NET app, but I'm not sure if I have done so correctly. It feels like I'm using a mix of FirebaseAuth and .NET authentication using cookie based authentication.
I started by generating a firebase token by succesfully signing in the user:
FirebaseAuthProvider firebaseAuthProvider = new FirebaseAuthProvider(new FirebaseConfig("API_KEY"));
FirebaseAuthLink firebaseAuthLink = await firebaseAuthProvider.SignInWithEmailAndPasswordAsync(Input.Email, Input.Password);
var firebaseToken = firebaseAuthLink.FirebaseToken;
I then created custom claims, and called HttpContext.SignInAsync using these claims to be fully signed in according to .NET and use [Authorize] on my Razor pages:
var claims = new List<Claim>()
{
new Claim(ClaimTypes.NameIdentifier, Convert.ToString(user.LocalId)),
new Claim(ClaimTypes.Role, userInfo.Role),
new Claim("Email", user.Email),
new Claim("FirstName", userInfo.FirstName),
new Claim("LastName", userInfo.LastName),
new Claim("Address", userInfo.Address),
new Claim("FirebaseToken", firebaseToken)
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties());
I see that you can also add custom claims through FirebaseAuth, so am I doing something wrong by calling HttpContext.SignInAsync? Is there an easier way to be authenticated simply by purely using FirebaseAuth?
I have a Web API backend that has an authentication endpoint for retrieving both the access and refresh token. My client already retrieves the access token and creates a new identity to sign in the user, using the HttpContext.
But how do I automatically get a new access token using the refresh token in my ASP.NET MVC client.
I already have the entire backend working for this part.
I've tried adding the Bearer authentication scheme to my client' services.
Now I'm using the Cookie authentication scheme.
Adding the authentication to the service:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => { options.LoginPath = "/Home/Index"; });
And here is how I log the user in:
var token = JsonConvert.DeserializeObject<Token>(result);
AuthenticationProperties options = new AuthenticationProperties()
{
AllowRefresh = true,
IsPersistent = true,
ExpiresUtc = DateTimeOffset.FromUnixTimeMilliseconds(token.Expiry),
};
// TODO: new claim for the user name
var claims = new[]
{
new Claim(ClaimTypes.Name, dto.Email),
new Claim(ClaimTypes.Role, dto.Role),
new Claim("AccessToken", $"Bearer {token.AccessToken}"),
};
var identity = new ClaimsIdentity(claims, "ApplicationCookie");
var principal = new ClaimsPrincipal(identity);
HttpContext.SignInAsync(principal, options);
Maybe this isn't the best way to authenticate my client but It worked so far.
I need the back-end and client to be separated because a mobile client also uses the same back-end.
Thanks in advance.
I'm making an API for Exact Online for a website with a form. The visitor will fill in his information and after that the visitor sends it. It need to be send to the Exact online account from my client. But before that I need a accesstoken. The problem is that I don't want to give the user the login page that Exact gives me. I'm searching already for days to find a way to skip the login or to enter the login information by backend (there is always 1 login, and that is the login from my client).
Now this authorization thing is something new for me. So far I know I can call my authorization settings from the startup with this:
HttpContext.Authentication.GetAuthenticateInfoAsync("ExactOnline");
But then I get that loginscreen that I don't want. The only thing that Exact is telling me to do:
Create an app registration that supports an automated connection wizard (your provisioning process).
Is there a way to send them the login information and the visitor doesn't see a loginpage.
In my Startup.cs
var s = new OAuthOptions{
AuthenticationScheme = "ExactOnline",
ClientId = "CLIENTID",
ClientSecret = "CLIENTSECRET",
CallbackPath = new PathString("/callback"),
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AuthorizationEndpoint = new Uri(string.Format("{0}/api/oauth2/auth", "https://start.exactonline.nl")).ToString(),
TokenEndpoint = new Uri(string.Format("{0}/api/oauth2/token", "https://start.exactonline.nl")).ToString(),
//Scope = { "identity", "roles" },
Events = new OAuthEvents
{
OnCreatingTicket = context =>
{
context.Identity.AddClaim(new Claim("urn:token:exactonline", context.AccessToken));
return Task.FromResult(true);
}
}
};
app.UseOAuthAuthentication(s);
First i had this code, but that gives me a null exception when i put the identity in the claimprincipal, probably because my claimsprincipal is null and i don't know why.
HttpContext.Authentication.AuthenticateAsync("ExactOnline");
var identity = new ClaimsIdentity("ExactOnline",ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
identity.Label = "Authentication";
identity.AddClaim(new Claim(ClaimTypes.Name, "USERNAME?"));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "PASSWORD?"));
claimsPrincipal.AddIdentity(identity);
var test = HttpContext.Authentication.SignInAsync("ExactOnline", claimsPrincipal, new AuthenticationProperties() { IsPersistent = false }));
After that i tried following code, but that also didn't work. My code will continue, but the test variable will be filled with this message: The name 'InnerExceptionCount' does not exist in the current context.
var identity = new ClaimsIdentity("ExactOnline", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
identity.Label = "Authentication";
identity.AddClaim(new Claim("username", "USERNAME"));
identity.AddClaim(new Claim("password", "PASSWORD"));
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(identity);
var test = HttpContext.Authentication.SignInAsync("ExactOnline", claimsPrincipal, new AuthenticationProperties() { IsPersistent = false });
Someone know how to solve this problem?
There are millions of guides out there, and none of them seem to do what I need. I am creating an Authentication Server, that simply just needs to issue, and validate/reissue tokens. So I can't create a middleware class to "VALIDATE" the cookie or header. I am simply receiving a POST of the string, and I need to validate the token that way, instead of the Authorize middleware that .net core provides.
My Startup Consists of the only Token Issuer Example I could get working.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
var secretKey = "mysupersecret_secretkey!123";
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));
var options = new TokenProviderOptions
{
// The signing key must match!
Audience = "AllApplications",
SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),
Issuer = "Authentication"
};
app.UseMiddleware<TokenProviderMiddleware>(Microsoft.Extensions.Options.Options.Create(options));
I can use the middleware on creation since I just need to intercept the body for the username and password. The middleware takes in the options from the previous Startup.cs code, checks the Request Path and will Generate the token from the context seen below.
private async Task GenerateToken(HttpContext context)
{
CredentialUser usr = new CredentialUser();
using (var bodyReader = new StreamReader(context.Request.Body))
{
string body = await bodyReader.ReadToEndAsync();
usr = JsonConvert.DeserializeObject<CredentialUser>(body);
}
///get user from Credentials put it in user variable. If null send bad request
var now = DateTime.UtcNow;
// Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
// You can add other claims here, if you want:
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, JsonConvert.SerializeObject(user)),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, now.ToString(), ClaimValueTypes.Integer64)
};
// Create the JWT and write it to a string
var jwt = new JwtSecurityToken(
issuer: _options.Issuer,
audience: _options.Audience,
claims: claims,
notBefore: now,
expires: now.Add(_options.Expiration),
signingCredentials: _options.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
///fill response with jwt
}
This large block of code above will Deserialize the CredentialUser json and then execute a stored procedure that returns the User Object. I will then add three claims, and ship it back.
I am able to successfully generate a jwt, and using an online tool like jwt.io, I put the secret key, and the tool says it is valid, with an object that I could use
{
"sub": " {User_Object_Here} ",
"jti": "96914b3b-74e2-4a68-a248-989f7d126bb1",
"iat": "6/28/2017 4:48:15 PM",
"nbf": 1498668495,
"exp": 1498668795,
"iss": "Authentication",
"aud": "AllApplications"
}
The problem I'm having is understanding how to manually check the claims against the signature. Since this is a server that issues and validates tokens. Setting up the Authorize middleware is not an option, like most guides have. Below I am attempting to Validate the Token.
[Route("api/[controller]")]
public class ValidateController : Controller
{
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Validate(string token)
{
var validationParameters = new TokenProviderOptions()
{
Audience = "AllMyApplications",
SigningCredentials = new
SigningCredentials("mysupersecret_secretkey!123",
SecurityAlgorithms.HmacSha256),
Issuer = "Authentication"
};
var decodedJwt = new JwtSecurityTokenHandler().ReadJwtToken(token);
var valid = new JwtSecurityTokenHandler().ValidateToken(token, //The problem is here
/// I need to be able to pass in the .net TokenValidParameters, even though
/// I have a unique jwt that is TokenProviderOptions. I also don't know how to get my user object out of my claims
}
}
I stole borrowed this code mostly from the ASP.Net Core source code: https://github.com/aspnet/Security/blob/dev/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs#L45
From that code I created this function:
private string Authenticate(string token) {
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
List<Exception> validationFailures = null;
SecurityToken validatedToken;
var validator = new JwtSecurityTokenHandler();
// These need to match the values used to generate the token
TokenValidationParameters validationParameters = new TokenValidationParameters();
validationParameters.ValidIssuer = "http://localhost:5000";
validationParameters.ValidAudience = "http://localhost:5000";
validationParameters.IssuerSigningKey = key;
validationParameters.ValidateIssuerSigningKey = true;
validationParameters.ValidateAudience = true;
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal;
try
{
// This line throws if invalid
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
// If we got here then the token is valid
if (principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
return principal.Claims.Where(c => c.Type == ClaimTypes.Email).First().Value;
}
}
catch (Exception e)
{
_logger.LogError(null, e);
}
}
return String.Empty;
}
The validationParameters need to match those in your GenerateToken function and then it should validate just fine.
I need to upgrade (or downgrade) my Website to using a local login page. I had it all working using the hybrid flow using the following code
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions(){});
And then when the token would come back, it would give me access to complete the authentication logic in asp.net- setting the claims identity, principal, etc.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
{
Notifications = new OpenIdConnectAuthenticationNotifications()
{
SecurityTokenValidated = async n =>
{
// perform transform, etc..
n.AuthenticationTicket = new AuthenticationTicket(
identity, n.AuthenticationTicket.Properties);
await Task.FromResult(0);
}
}
});
Now, I am going to be collecting the username and password from an MVC action method. I am able to get the access token from the client this way.
[HttpPost]
public ActionResult Login(LoginModel model)
{
var client = new TokenClient(
StsSettings.TokenEndpoint,
ClientId,
Secret);
var x = client.RequestResourceOwnerPasswordAsync(model.UserName, model.Password, "customid openid").Result;
return View(model);
}
But I'm not sure how the easiest way to tell ASP.NET to point to my custom login page instead of an identity server. Would I use forms authentication logic and create some AuthenticationTicket? Also, what is the best way set the ClaimsIdentity (I know how to get the claims back, just need a "hook")
If you want the outcome of the resource owner password flow to be the logged in user, you need to issue the main authentication cookie with the claims you have for that newly authenticated user.
var claims = new Claim[] {
new Claim("name", username),
new Claim("sub", "4848784904"),
new Claim("email", "BrockAllen#gmail.com"),
new Claim("role", "Admin"),
new Claim("role", "Dev"),
};
// "Cookies" is the name of your cookie middleware,
// so change to match what you're actually using in Startup.cs
var ci = new ClaimsIdentity(claims, "Cookies", "name", "role");
Request.GetOwinContext().Authentication.SignIn(ci);
return Redirect("~/Home/Secure");