I'm wondering the best pratice to use refresh-token with JwtAuthProviderReader. At the moment when my jwt expires I send a request /access-token to get a new one.
var jwt = authClient.Send(new GetAccessToken() {RefreshToken = Request.GetCookieValue("ss-refreshtok") }).AccessToken;
Response.SetCookie(new Cookie()
{
Path = "/",
Name = "ss-tok",
Value = jwt
});
My problem is I get "Token has expired" even though I already set the new jwt to the cookie. I have to refresh the page a few time before it's valid...
Here is my Authenticate Service :
public class AuthenticationHandler: Service
{
private readonly JsonServiceClient authClient;
public AuthenticationHandler()
{
authClient = new JsonServiceClient("http://localhost/authentication/");
}
[Authenticate]
public GetAuthenticationContextResponse Get(GetAuthenticationContext request)
{
var authSession = this.SessionAs<MyAbaxAuthSession>();
return new GetAuthenticationContextResponse
{
CustomerId = authSession.CustomerId,
UserId = int.Parse(authSession.UserAuthId)
};
}
public UserAuthenticateResponse Post(UserAuthenticate request)
{
var response = authClient.Send(new Authenticate
{
provider = "credentials",
UserName = request.UserName,
Password = request.Password,
UseTokenCookie = true
});
Response.SetCookie(new Cookie()
{
Path = "/",
Name = "ss-tok",
Value = response.BearerToken
});
Response.SetCookie(new Cookie()
{
Path = "/",
Name = "ss-refreshtok",
Value = response.RefreshToken
});
return new UserAuthenticateResponse();
}
}
Please refer to the JWT docs on how to access your JWT RefreshToken, i.e. It's returned in RefreshToken property after a successful Authentication:
var response = client.Post(new Authenticate {
provider = "credentials",
UserName = userName,
Password = password,
});
var jwtToken = response.BearerToken;
var refreshToken = response.RefreshToken;
Related
I am working on an ASP.NET MVC 5 website using EPiServer CMS.
The requirement is that we have admin (back-end users) log in using ADFS (working) and OIDC for front-end.
Both are set to passive mode and are called through action methods in a controller.
The issue I am facing is that front-end users OIDC is set to (Authentication type ="Federation") when it returns from external call instead of external cookie.
public void Configuration(IAppBuilder app)
{
//IdentityModelEventSource.ShowPII = true;
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11;
ConfigureMemberAuth(app);
ConfigureAdminAuth(app);
}
private void ConfigureMemberAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/logmein/"),
LogoutPath = new PathString("/"),
AuthenticationMode = AuthenticationMode.Active
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
_ = app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = ApplicationSettings.OIDCClientId,
ClientSecret = ApplicationSettings.OIDCClientSecret,
Authority = ApplicationSettings.OIDCAuthority,
RedirectUri = $"{ApplicationSettings.Domain}{_oidcRedirectMethod}",
PostLogoutRedirectUri = ApplicationSettings.Domain,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
Scope = "xxx",
SaveTokens = true,
AuthenticationMode = AuthenticationMode.Passive,
AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = notification =>
{
if (string.Equals(notification.ProtocolMessage.Error, "xxx", StringComparison.Ordinal))
{
notification.HandleResponse();
if (string.Equals(notification.ProtocolMessage.ErrorDescription, "xxx", StringComparison.Ordinal))
notification.Response.Redirect(ApplicationSettings.Domain);
else
{
var errorPage = UrlResolver.GetUrl(PageHelper.GetAdminPage().LoginBox.NemIDErrorPage.GetFriendlyUrl());
notification.Response.Redirect(errorPage);
}
}
return Task.CompletedTask;
},
// Retrieve an access token from the remote token endpoint
// using the authorization code received during the current request.
AuthorizationCodeReceived = async notification =>
{
using (var client = new HttpClient())
{
var configuration = await notification.Options.ConfigurationManager.GetConfigurationAsync(notification.Request.CallCancelled);
var tokenEndpointResult = await ExchangeCodeForTokens(notification, client, configuration);
// Add the identity token to the returned ClaimsIdentity to make it easier to retrieve.
notification.AuthenticationTicket.Identity.AddClaim(new Claim(
type: OpenIdConnectParameterNames.IdToken,
value: tokenEndpointResult.Value<string>(OpenIdConnectParameterNames.IdToken)));
// Retrieve the claims from UserInfo endpoint using the access token as bearer token.
var accesstoken = tokenEndpointResult.Value<string>(OpenIdConnectParameterNames.AccessToken);
var userInfoEndpointResult = await UserInfoEndpointClaims(notification, client, configuration, accesstoken);
//Security note: It is important to verify that the sub claim from ID token matches the sub claim in the UserInfo response
var userinfoSub = userInfoEndpointResult["xx"].Value<string>();
var idtokenSub = notification.AuthenticationTicket.Identity.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value;
if (userinfoSub == idtokenSub)
{
//add claims from UserInfo endpoint to identity
foreach (var entry in userInfoEndpointResult)
{
if (!notification.AuthenticationTicket.Identity.HasClaim(c => c.Type == entry.Key))
{
notification.AuthenticationTicket.Identity.AddClaim(new Claim(
type: entry.Key,
value: entry.Value.ToString()));
}
}
// Add access token to claims.
notification.AuthenticationTicket.Identity.AddClaim(new Claim(
OpenIdConnectParameterNames.AccessToken,
accesstoken));
}
}
},
// Attach the id_token stored in the authentication cookie to the logout request.
RedirectToIdentityProvider = notification =>
{
if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var token = notification.OwinContext.Authentication.User?.FindFirst(OpenIdConnectParameterNames.IdToken);
if (token != null)
{
notification.ProtocolMessage.IdTokenHint = token.Value;
}
notification.Response.Redirect(ApplicationSettings.Domain);
}
return Task.CompletedTask;
},
}
});
}
private void ConfigureAdminAuth(IAppBuilder app)
{
//Enable cookie authentication, used to store the claims between requests
app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
CookieName = WsFederationAuthenticationDefaults.CookieName,
AuthenticationMode = AuthenticationMode.Active,
CookieHttpOnly = true,
});
//Enable federated authentication
_ = app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions()
{
//Trusted URL to federation server meta data
MetadataAddress = ApplicationSettings.MetaDataAddress,
//Value of Wtreal must *exactly* match what is configured in the federation server
Wtrealm = ApplicationSettings.RelyPartyUri,
AuthenticationMode = AuthenticationMode.Passive,
Notifications = new WsFederationAuthenticationNotifications()
{
RedirectToIdentityProvider = (ctx) =>
{
// To avoid a redirect loop to the federation server send 403 when user is authenticated but does not have access
if (ctx.OwinContext.Response.StatusCode == 401 && (ctx.OwinContext.Authentication.User.Identity.AuthenticationType == WsFederationAuthenticationDefaults.AuthenticationType && ctx.OwinContext.Authentication.User.Identity.IsAuthenticated))
{
ctx.OwinContext.Response.StatusCode = 403;
ctx.HandleResponse();
}
//XHR requests cannot handle redirects to a login screen, return 401
if (ctx.OwinContext.Response.StatusCode == 401 && IsXhrRequest(ctx.OwinContext.Request))
{
ctx.HandleResponse();
}
return Task.FromResult(0);
},
SecurityTokenValidated = (ctx) =>
{
//Ignore scheme/host name in redirect Uri to make sure a redirect to HTTPS does not redirect back to HTTP
var redirectUri = new Uri(ctx.AuthenticationTicket.Properties.RedirectUri, UriKind.RelativeOrAbsolute);
if (redirectUri.IsAbsoluteUri)
{
ctx.AuthenticationTicket.Properties.RedirectUri = redirectUri.PathAndQuery;
}
//Sync user and the roles to EPiServer in the background
ServiceLocator.Current.GetInstance<ISynchronizingUserService>().SynchronizeAsync(ctx.AuthenticationTicket.Identity);
return Task.FromResult(0);
},
}
});
//Add stage marker to make sure WsFederation runs on Authenticate (before URL Authorization and virtual roles)
app.UseStageMarker(PipelineStage.Authenticate);
// Remap logout to a federated logout
app.Map(LogoutUrl, map =>
{
map.Run(ctx =>
{
ctx.Authentication.SignOut();
ctx.Response.Redirect(ApplicationSettings.Domain);
return Task.FromResult(0);
});
});
}
When I out comment this line: app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);
in admin auth then client-login works as intended and authentication type is set to "ExternalCookie" when call return but then admin login stops working ?
Any help would be appreciated
We are implementing Azure SSO in Traditional ASP.Net Web Application and we want to implement Authorization Code Flow for generating Refresh, Access and Id Tokens.
We have implemented the below code in AuthorizationCodeReceived function of the owin's app.UseOpenIdConnectAuthentication class. From the below mentioned code we are able to successfully fetch the Refreshtoken, AccessToken and IdToken.
But notification.AuthenticationTicket is null and it throws null reference excpetion so we are not able to add the claims for id and access tokens.
Also in the aspx.cs file the HttpContext.Current.User.Identity.IsAuthenticated is returned as false even after generating all the 3 tokens.
Please suggest why notification.AuthenticationTicket is null inside AuthorizationCodeReceived event and what changes we have to do inside AuthorizationCodeReceived event to make HttpContext.Current.User.Identity.IsAuthenticated as "true".
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseKentorOwinCookieSaver();
app.UseCookieAuthentication(new CookieAuthenticationOptions());
Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectProtocolValidator dd = new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectProtocolValidator();
dd.RequireNonce = false;
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
ClientSecret = clientSecret,
PostLogoutRedirectUri = redirectUri,
RedirectUri = redirectUri,
Scope = "openid profile email offline_access",
ResponseType = OpenIdConnectResponseType.Code,
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = (context) =>
{
return System.Threading.Tasks.Task.FromResult(0);
},
AuthorizationCodeReceived = async notification =>
{
using (var client = new HttpClient())
{
var configuration = await notification.Options.ConfigurationManager.GetConfigurationAsync(notification.Request.CallCancelled);
var request = new HttpRequestMessage(HttpMethod.Post, configuration.TokenEndpoint);
request.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
[OpenIdConnectParameterNames.ClientId] = notification.Options.ClientId,
[OpenIdConnectParameterNames.ClientSecret] = notification.Options.ClientSecret,
[OpenIdConnectParameterNames.Code] = notification.ProtocolMessage.Code,
[OpenIdConnectParameterNames.GrantType] = "authorization_code",
[OpenIdConnectParameterNames.RedirectUri] = notification.Options.RedirectUri
});
var response = await client.SendAsync(request, notification.Request.CallCancelled);
response.EnsureSuccessStatusCode();
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
notification.AuthenticationTicket.Identity.AddClaim(new Claim(
type: OpenIdConnectParameterNames.AccessToken,
value: payload.Value<string>(OpenIdConnectParameterNames.AccessToken)));
notification.AuthenticationTicket.Identity.AddClaim(new Claim(
type: OpenIdConnectParameterNames.IdToken,
value: payload.Value<string>(OpenIdConnectParameterNames.IdToken)));
}
},
// Attach the id_token stored in the authentication cookie to the logout request.
RedirectToIdentityProvider = notification =>
{
if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var token = notification.OwinContext.Authentication.User?.FindFirst(OpenIdConnectParameterNames.IdToken);
if (token != null)
{
notification.ProtocolMessage.IdTokenHint = token.Value;
}
}
return Task.CompletedTask;
},
SecurityTokenValidated = (context) =>
{ if (context != null)
{
if (context.ProtocolMessage != null && !string.IsNullOrEmpty(context.ProtocolMessage.IdToken))
{
context.AuthenticationTicket.Identity.AddClaim(new Claim("IdToken", context.ProtocolMessage.IdToken));
}
}
return Task.FromResult(0);
}
}
}
);
We are using OpenIdConnect based authentication in the asp.net mvc application. Initial login is working fine. But when we use the Ajax call to invoke the action method, User is coming as not authenticated. I checked in Custom Authorization - HttpContext.Request.IsAuthenticated is coming as false.
I checked the cookie ".AspNet.Cookies" and it has the value. Why is open ID not authenticating the user.
Below is my authentication code
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = AuthenticationConfig.ClientId,
Authority = AuthenticationConfig.AADInstance + AuthenticationConfig.TenantId,
PostLogoutRedirectUri = AuthenticationConfig.PostLogoutRedirectURI,
RedirectUri = AuthenticationConfig.RedirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
ResponseType = OpenIdConnectResponseType.Code,
SaveTokens = true,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(AuthenticationConfig.ClientSecret)),
ValidateIssuer = true,
ValidIssuer = AuthenticationConfig.AADInstance + AuthenticationConfig.TenantId + "/v2.0",
},
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// when an auth code is received...
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
SecurityTokenValidated = async n =>
{
var nid = new ClaimsIdentity(n.AuthenticationTicket.Identity);
//var claimsIdentity = filterContext.HttpContext.User.Identity as ClaimsIdentity;
var user = nid.Claims.Where(r => r.Type == PreferedUserNameClaimType).Select(v => v.Value).FirstOrDefault();
var userRolesroles = GetRolesForUser(user);
//nid.AddClaim(new Claim("expires_at", DateTimeOffset.Now.AddSeconds(int.Parse(n.ProtocolMessage.ExpiresIn)).ToString()));
userRolesroles.ToList().ForEach(ui => nid.AddClaim(new Claim(ClaimTypes.Role, ui)));
// keep the id_token for logout
nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
// add access token for sample API
nid.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
UserService.SetUserInformation(user);
},
RedirectToIdentityProvider = ctx =>
{
bool isAjaxRequest = (ctx.Request.Headers != null && ctx.Request.Headers["X-Requested-With"] == "XMLHttpRequest");
if (ctx.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
{
if (isAjaxRequest && ctx.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
ctx.Response.Headers.Remove("Set-Cookie");
ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
ctx.HandleResponse();
return Task.FromResult(0);
}
}
return Task.FromResult(0);
}
}
});
}
Usually in asp.net, the ApiControllers has no concept of your Controller's authentication. Depending on the way things are builr, you need to add an Authorization header with a bearer access token to let the API know about the authenticated user.
I am trying to build a method which validates my tokens. I am retrieving my tokens from Azure Active Directory with Open Id Connect Authorization Code Flow. The tokens that I get are the access_token and the id_token. I am using .NET Core.
My validation code is as follows:
string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
var handler = new JwtSecurityTokenHandler();
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
try
{
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidIssuers = new [] { "https://login.microsoftonline.com/tenantid/v2.0" },
ValidAudiences = new [] { "client-Id" },
ValidateAudience = true,
ValidateIssuer = true,
IssuerSigningKeys = config.SigningKeys,
ValidateLifetime = true
};
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken validatedToken = null;
tokenHandler.ValidateToken(token.AccessToken, validationParameters, out validatedToken);
return validatedToken != null;
}
catch (SecurityTokenInvalidSignatureException ex)
{
return false;
}
catch(SecurityTokenValidationException)
{
return false;
}
The code below works for the id_token BUT
does not work for the access_token
The error message which I am getting when this method is executed for the access_token is:
IDX10511: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.X509SecurityKey , KeyId: CtAAALb-8NsDe333734859crfOc
'.
kid: 'CtAAALb-8NsDe333734859crfOc'.
Exceptions caught:
' '
the nonce header has to be SHA2 hashed before signature verification
Here is an example of code where you can see
jsonToken.Header.Add("nonce", hashedNonce);
private static bool _hashNonceBeforeValidateToken = true;
private const string MicrosoftGraphApplicationId = "00000003-0000-0000-c000-000000000000";
private const string MicrosoftIssuer = "https://sts.windows.net";
public static bool ValidateTokenSignature(string accessToken, ApplicationConfiguration applicationConfiguration) {
var tokenHandler = new JwtSecurityTokenHandler();
var jsonToken = tokenHandler.ReadJwtToken(accessToken);
string[] parts = accessToken.Split('.');
string header = parts[0];
string payload = parts[1];
string signature = parts[2];
//hash nonce and update header with the hash before validating
if (_hashNonceBeforeValidateToken &&
jsonToken.Header.TryGetValue("nonce", out object nonceAsObject))
{
string plainNonce = nonceAsObject.ToString();
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashedNonceAsBytes = sha256.ComputeHash(
System.Text.Encoding.UTF8.GetBytes(plainNonce));
string hashedNonce = Base64Url.Encode(hashedNonceAsBytes);
jsonToken.Header.Remove("nonce");
jsonToken.Header.Add("nonce", hashedNonce);
header = tokenHandler.WriteToken(jsonToken).Split('.')[0];
accessToken = $"{header}.{payload}.{signature}";
}
}
//get the Microsoft JWT signature public key
string stsDiscoveryEndpoint = $"https://login.microsoftonline.com/{applicationConfiguration.TenantId}/v2.0/.well-known/openid-configuration";
if (jsonToken.Header.TryGetValue("ver", out object version) && version.ToString() == "1.0")
{
stsDiscoveryEndpoint = $"https://login.microsoftonline.com/{applicationConfiguration.TenantId}/.well-known/openid-configuration";
}
var openidConfigManaged = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint,
new OpenIdConnectConfigurationRetriever(),
new HttpDocumentRetriever());
var configTask = openidConfigManaged.GetConfigurationAsync();
configTask.Wait();
var config = configTask.Result;
var parameteres = new TokenValidationParameters()
{
RequireAudience = true,
ValidateAudience = true,
ValidAudiences = new[] { applicationConfiguration.ApplicationId, MicrosoftGraphApplicationId },
ValidateIssuer = true,
ValidIssuers = new string[] { $"{MicrosoftIssuer}/{applicationConfiguration.TenantId}/", config.Issuer },
IssuerSigningKeys = config.SigningKeys,
ValidateIssuerSigningKey = true,
RequireExpirationTime = true,
ValidateLifetime = true,
};
var claimPrincipal = tokenHandler.ValidateToken(
accessToken, parameteres, out SecurityToken validatedToken);
return claimPrincipal.Identity.IsAuthenticated;
}
Is the access_token audience your API or Microsoft Graph/other 3rd party service? It only makes sense to validate the tokens that you (your service) consumes, other audiences will take care of this on their own. On top of that, the signature of that JWT may be opaque to you.
See this for more - https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/812#issuecomment-456700813
I am trying to implement login with Facebook and Twitter. I got it working with Facebook but don't know how to do it with Twitter. Are there any examples? My Facebook implementation is like this:
var auth = new OAuth2Authenticator(
clientId: "*****************",
scope: "email",
authorizeUrl: new System.Uri("https://m.facebook.com/dialog/oauth/"),
redirectUrl: new System.Uri("http://www.facebook.com/connect/login_success.html"));
StartActivity(auth.GetUI(this));
auth.Completed += (senderFb, eventArgs) =>
{
if (eventArgs.IsAuthenticated)
{
AccountStore.Create(this).Save(eventArgs.Account, "Facebook");
// Now that we're logged in, make a OAuth2 request to get the user's info.
var request = new OAuth2Request("GET", new System.Uri("https://graph.facebook.com/me"), null, eventArgs.Account);
request.GetResponseAsync().ContinueWith(t =>
{
if (!t.IsFaulted && !t.IsCanceled)
{
var obj = JsonValue.Parse(t.Result.GetResponseText());
if (obj != null)
{
var user = new UserProfile
{
FirstName = obj["first_name"],
LastName = obj["last_name"],
FacebookProfileLink = obj["link"],
FacebookToken = eventArgs.Account.Properties["access_token"],
Gender = obj["gender"] == "female" ? "Female" : "Male",
EmailAddress = obj["email"],
DisplayName = obj["name"],
Name = obj["name"],
LoginName = obj["email"]
};
SignUpUser(user);
}
}
}, uiScheduler);
}
};
You can use oauth1 like this.
this.Authenticator = new OAuth1Authenticator (ConsumerKey, ConsumerSecret, RequestTokenUrl, AuthorizeUrl, AccessTokenUrl, DummyCallBackUrl, (IDictionary<string, string> accountProperties) => {
string screen_name = "";
if (accountProperties.TryGetValue("screen_name", out screen_name)) {
Account a = new Account(screen_name, accountProperties);
AuthenticatorCompletedEventArgs e = new AuthenticatorCompletedEventArgs(a);
CompleteAuthentication(e);
}
return null;
});
twitter api dosen't fully support OAuth2