Redirect back to the ASP.NET Mvc Client after Sign-out from IdentityServer - asp.net-mvc

I want to redirect back to my client after sign-out from local, then the IS4; My AspNetCore Mvc client works correctly and redirect back to the client after sign-out, but the AspNet Mvc (not Core) it doesn't.
here is my Startup.Configuration method:
public void Configuration(IAppBuilder app)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
SignInAsAuthenticationType = "Cookies",
Authority = "https://localhost:5000",
UseTokenLifetime = false,
// RedeemCode = true,
ClientId = "aspNet_client",
ClientSecret = "secret",
RedirectUri = "https://localhost:44343/sigin-oidc",
PostLogoutRedirectUri = "https://localhost:44343/signout-callback-oidc",
SaveTokens = true,
ResponseType = "code id_token",
Scope = "openid profile offline_access",
TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = JwtClaimTypes.PreferredUserName,
RoleClaimType = JwtClaimTypes.Role,
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = onAuthenticationFailed,
MessageReceived = onMessageReceived,
// AuthorizationCodeReceived = onAuthorizationCodeReceived
}
});
}
I used this method to sign-out:
public ActionResult SignOut()
{
Request.GetOwinContext().Authentication.SignOut();
return Redirect("/");
}
I used this method too:
public ActionResult SignOut()
{
System.Web.HttpContext.Current.GetOwinContext().Authentication.SignOut(
new AuthenticationProperties
{
RedirectUri = "https://localhost:44343"
},
CookieAuthenticationDefaults.AuthenticationType,
OpenIdConnectAuthenticationDefaults.AuthenticationType
);
//"Cookies", "OpenIdConnect"
}
But not worked. So my question is:
How to automatic redirect back to my AspNetMvc Client after sign-out?

This was an error reported long time ago on IdentityServer3. It got fixed here by setting IdTokenHint on logout. In this case as we use IdentityServer4, we can implement similar fix manually on ASP.NET MVC app. Here is changes need to make:
on IdentityServer project set PostLogoutRedirectUris for the client:
new Client
{
ClientId = "aspNet_client",
//All other settings ...
PostLogoutRedirectUris = { "http://localhost:44343" },
},
On ASP.NET mvc application, set OpenIdConnectAuthenticationOptions - PostLogoutRedirectUri to the same value as step 1
Change Notifications - SecurityTokenValidated and RedirectToIdentityProvider to set IdTokenHint on logout
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
// other settings...
PostLogoutRedirectUri = "http://localhost:44343",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = n =>
{
n.AuthenticationTicket.Identity.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
return Task.FromResult(0);
},
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var id_token_claim = n.OwinContext.Authentication.User.Claims.FirstOrDefault(x => x.Type == "id_token");
if (id_token_claim != null)
{
n.ProtocolMessage.IdTokenHint = id_token_claim.Value;
}
}
return Task.FromResult(0);
}
}
});
If you want to redirect automatically set AccountOptions - AutomaticRedirectAfterSignOut to true on IdentityServer, default value is false.
Implemented it myself here

Related

How to use MVC4 Client with IdentityServer4?

Does anyone know how to have MVC 4 client app to use identityserver4 as auth provider?
I have tried the sample codes of identityserver3 but no success. Upon request to [Authorize] action it redirects to identityserver4 probably login end point and gives unknown error.
As far as I know, I am not able to define client at both identityserver4 'start-up.cs' and MVC client with OWIN's 'startup.cs'.
Update
The code from my IdentityServer4 app - MVC 4 Client Definition
// OpenID Connect hybrid flow and client credentials client (MVC)
new Client
{
ClientId = "mvc4",
ClientName = "MVC 4 Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
RequireConsent = false,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:53173/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:53173/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
},
AllowOfflineAccess = true
}
And the code from 'Startup.cs' of my MVC 4 app
public void Configuration(IAppBuilder app)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary<string, string>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "http://localhost:5000/",
RequireHttpsMetadata = false,
ClientId = "mvc4",
ClientSecret = "secret",
ResponseType = "code id_token",
Scope = "openid profile api1 offline_access",
UseTokenLifetime = false,
SignInAsAuthenticationType = "Cookies",
});
}
Update 2
I changed the Startup.cs of my MVC 4 Client to:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
RedirectUri = "http://localhost:53173/signin-oidc",
ClientId = "mvc4",
ClientSecret = "secret",
ResponseType = "code id_token"
});
It now presents a login page, logs in the user and then the IdentityServer has gone into never ending loop:
Update 3
public void Configuration(IAppBuilder app)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary<string, string>();
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
RedirectUri = "http://localhost:53173/signin-oidc",
ClientId = "mvc4",
ClientSecret = "secret",
ResponseType = "code id_token",
Scope = "openid profile api1 offline_access",
AuthenticationMode = AuthenticationMode.Active
});
}
As recommended added the scopes but still there is a loop; the request swings between MVC4 client and IdentityServer4.
Update 4
Solved - Check my answer.
I finally got it working.
Firstly, there is a bug (Katana Bug #197) in the OWIN which makes it to handle the tokens rather 'awkwardly'. So a workaround is nuget package Kentor.OwinCookieSaver by Kentor. One will need to install at the MVC4 Client.
Thereafter, modify the client configuration as under:-
new Client
{
ClientId = "mvc4",
ClientName = "MVC 4 Web Client",
AllowedGrantTypes = {
GrantType.Hybrid,
GrantType.ClientCredentials
},
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:53173/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:53173/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
},
AllowOfflineAccess = true
}
Modify the Configuration of 'Startup.cs' at MVC4 client as under
public void Configuration(IAppBuilder app)
{
app.UseKentorOwinCookieSaver();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary<string, string>();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
RedirectUri = "http://localhost:53173/signin-oidc",
ClientId = "mvc4",
ClientSecret = "secret",
ResponseType = OpenIdConnectResponseType.CodeIdTokenToken,
Scope = "openid profile api1 offline_access",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = notification =>
{
notification.AuthenticationTicket.Identity.AddClaim(new Claim("id_token", notification.ProtocolMessage.IdToken));
notification.AuthenticationTicket.Identity.AddClaim(new Claim("access_token", notification.ProtocolMessage.AccessToken));
return Task.FromResult(0);
},
RedirectToIdentityProvider = notification =>
{
return Task.FromResult(0);
}
}
});
Rebuild Solution >> Clean and Run. Now you can use IdentityServer4 oidc for MVC4 Client.
I would recommend you review all URLs and make sure that they are all identical and there is no any extra / in Identity or client configuration.
One more thing, I can't see you scope in "Update 2".

Social Signout not working in conjunction with OpenIdConnect

I created an MVC Web Application with OpenIdConnect authentication (for Azure Authentication) and Authentication providers for Google, Facebook and Microsoft Account.
The Configuration in StartupAuth looks like this:
public void ConfigureAuth(IAppBuilder app)
{
if (Config.TaskboardUserSource == Config.DirectoryService.AzureAD)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
ExpireTimeSpan = new TimeSpan(6, 0, 0),
SlidingExpiration = true,
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Home/Index"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = Config.ClientId,
Authority = string.Format("{0}common", Config.AadInstance),
UseTokenLifetime = false,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
SecurityTokenValidated = (context) =>
{
return Task.FromResult(0);
},
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(Config.ClientId, Config.AppKey);
string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(string.Format("{0}{1}", Config.AadInstance, tenantID), new ADALTokenCache(signedInUserID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCodeAsync(
code,
new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)),
credential,
Config.GraphResourceID).Result;
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
// This ensures that the address used for sign in and sign out is picked up dynamically from the request
// this allows you to deploy your app (to Azure Web Sites, for example)without having to change settings
// Remember that the base URL of the address used here must be provisioned in Azure AD beforehand.
string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
context.ProtocolMessage.RedirectUri = appBaseUrl + "/";
context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
context.OwinContext.Response.Redirect("/Home/Index");
context.HandleResponse(); // Suppress the exception
return Task.FromResult(0);
}
}
});
var facebookAuthenticationOptions = new FacebookAuthenticationOptions()
{
AppId = Config.FBAppId,
AppSecret = Config.FBAppSecret,
UserInformationEndpoint = Config.FBUserInformationEndpoint
};
facebookAuthenticationOptions.Scope.Add("email");
app.UseFacebookAuthentication(facebookAuthenticationOptions);
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
ClientId = Config.GoogleClientId,
ClientSecret = Config.GoogleClientSecret
});
var microsoftOptions = new MicrosoftAccountAuthenticationOptions()
{
ClientId = Config.MSAppId,
ClientSecret = Config.MSAppSecret,
};
microsoftOptions.Scope.Add("wl.basic");
microsoftOptions.Scope.Add("wl.emails");
app.UseMicrosoftAccountAuthentication(microsoftOptions);
}
}
All authentication options work fine.
When I want to signout, the only signout working is OpenIdConnect Signout.
For all other authentication providers, the cookie is still available and just by clicking the "Logon" Button the secured pages are shown without asking for a password.
My Signout looks like this:
public void SignOut()
{
string callbackUrl = Url.Action("SignOutCallback", "Account", routeValues: null, protocol: Request.Url.Scheme);
HttpContext.GetOwinContext().Authentication.SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
HttpContext.GetOwinContext()
.Authentication.GetAuthenticationTypes()
.Select(o => o.AuthenticationType).ToArray());
HttpContext.GetOwinContext().Authentication.SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
CookieAuthenticationDefaults.AuthenticationType);
}
How can I make sure the user is signed out and gets redirected to the start page?
After I inserted a switch case statement in my signout code to do the signout for every logonprovider it finally works. Here is my code:
public async Task<ActionResult> SignOut()
{
var currentUser = await UserService.CurrentUser();
if (currentUser != null)
{
var redirectUrl = Request.GetBaseUrl();
var loginProviders = new string[] {
"Google",
"TwoFactorRememberBrowser",
"TwoFactorCookie",
"ExternalCookie",
"ApplicationCookie"
};
switch (currentUser.LoginProvider)
{
case LogonProvider.FacebookProviderKey:
{
loginProviders = new string[] {
"Facebook",
"TwoFactorRememberBrowser",
"TwoFactorCookie",
"ExternalCookie",
"ApplicationCookie" };
break;
}
case LogonProvider.GoogleProviderKey:
{
loginProviders = new string[] {
"Google",
"TwoFactorRememberBrowser",
"TwoFactorCookie",
"ExternalCookie",
"ApplicationCookie" };
//return new RedirectResult($"https://www.google.com/accounts/Logout");
break;
}
case LogonProvider.MicrosoftProviderKey:
{
loginProviders = new string[] {
"Microsoft",
"TwoFactorRememberBrowser",
"TwoFactorCookie",
"ExternalCookie",
"ApplicationCookie" };
break;
}
default:
{
loginProviders = new string[] {
"Office365",
"TwoFactorRememberBrowser",
"TwoFactorCookie",
"ExternalCookie",
"ApplicationCookie" };
break;
}
}
HttpContext.GetOwinContext().Authentication.SignOut(new AuthenticationProperties { RedirectUri = redirectUrl }, loginProviders);
}
return RedirectToAction("Index", "Home");
}

Identity server 3 client with mvc and api not refreshing access token

I have a working identity provider. The client I have designed to authenticate against it is a single project combining MVC and web API. The initial authentication is done me the MVC. If the access token becomes invalid it refreshes as expected.
MVC side:
public partial class Startup {
public void ConfigureAuth(IAppBuilder app)
{
//AntiForgeryConfig.UniqueClaimTypeIdentifier = "sub";
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
CookieName = "CookieName",
ReturnUrlParameter = "/Dashboard",
LogoutPath = new PathString("/"),
});
app.UseOpenIdConnectAuthentication(GetOpenIdConnectAuthenticationOptions());
}
private OpenIdConnectAuthenticationOptions GetOpenIdConnectAuthenticationOptions()
{
var options = new OpenIdConnectAuthenticationOptions
{
ClientId = "client.id",
Authority = AuthorityUrl,
RedirectUri = RedirectUri,
PostLogoutRedirectUri = RedirectUri,
ResponseType = "code id_token",
Scope = "openid profile email offline_access roles company utc_offset service_api",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
},
SignInAsAuthenticationType = "Cookies",
Notifications = GetOpenIdConnectAuthenticationNotifications()
};
return options;
}
private OpenIdConnectAuthenticationNotifications GetOpenIdConnectAuthenticationNotifications()
{
var container = UnityLazyInit.Container;
var authorizationProvider = container.Resolve<AuthorizationProvider>();
var notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
authorizationProvider.Authority = Authority;
authorizationProvider.LoginMethod = LoginMethod;
var tokenResponse = await authorizationProvider.GetAccessAndRefreshTokens(n);
var userInfoClaims = await authorizationProvider.GetUserInfoClaims(tokenResponse);
userInfoClaims = authorizationProvider.TransformUserInfoClaims(userInfoClaims);
// create new identity
var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(userInfoClaims);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));
var user = authorizationProvider.GetUser(id);
var applicationClaims = authorizationProvider.GetApplicationClaims(user);
id.AddClaims(applicationClaims);
var permisionClaims = authorizationProvider.GetPermisionClaims(user);
id.AddClaims(permisionClaims);
n.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
n.AuthenticationTicket.Properties);
},
RedirectToIdentityProvider = n =>
{
// if signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
}
};
return notifications;
}
}
The presentation layer (browser side) leverages angulerjs however I have not incorporated any support for authentication. I am relying on the MVC.
When you presentation layer makes calls to the API it automatically validates against the access token retrieved by the MVC but if it expires it is unable to refresh the access token. It also doesn’t return unauthorized. It appears to be trying to refresh but fails. The presentation receives the HTML for the error page of the identity provider when the api calls attempt to refresh the token.
How do I fix this? It seems to me is supposed to authenticate and refresh automatically for the MVC and the API when they are combined but this is not working for me.
note to clarify the start up configuration above is shared but the MVC and the API.
new Client
{
ClientName = "MVC Client",
ClientId = "client.id",
ClientSecrets = new List<Secret> {
new Secret("secret".Sha256())
},
Flow = Flows.Hybrid,
AllowedScopes = new List<string>
{
Constants.StandardScopes.OpenId,
Constants.StandardScopes.Profile,
Constants.StandardScopes.Email,
Constants.StandardScopes.OfflineAccess,
"roles",
"company",
"utc_offset",
"service_api
"
},
RequireConsent = false,
RedirectUris = new List<string>
{
REMOVED
},
PostLogoutRedirectUris = new List<string>
{
REMOVED
},
AllowedCorsOrigins = new List<string>
{
REMOVED
},
AccessTokenLifetime = 60,
IdentityTokenLifetime = 60,
AbsoluteRefreshTokenLifetime = 60 * 60 * 24,
SlidingRefreshTokenLifetime = 60 * 15,
},
#brockallen - The short of this is. I have an application that is MVC and WEBAPI and Anjulgarjs. I do not think a hybrid like this is wise but I inherited this application and now I have to find a way to make it work with Idnetity Server 3.
I would be grateful for any guidance. Please.
I was having the same issue as you. The problem is that you are setting a cookie for MVC, but you are not setting the expiration date, thus MVC does not know that the cookie has expired. What you need to do is set the expiration date for the AuthenticationTicket in the following way:
n.AuthenticationTicket.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddSeconds(int.Parse(n.ProtocolMessage.ExpiresIn));
n.AuthenticationTicket.Properties.IssuedUtc = DateTime.Now;
//Add encrypted MVC auth cookie
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
I've also set the issued date, but that's not mandatory

Connecting with Javascript to Web API protected by IdentityServer3

I have a Asp.NET MVC / WebAPI project with an embedded IdentityServer3.
I want both MVC and WebAPI to be protected by the IdentityServer. So I have used Authorize attribute on both MVC controllers and API controllers.
When surfing to my test page (which is protected) I get redirected to the IdentityServer login page. I enter my username and password and get authenticated and redirected back.
On the page I have a button that triggers a GET from javascript, with my access token in the authorization header, to my protected API. But here it fails with a 401 Unauthorized.
I get the access token to javascript by rendering it to the page with Razor.
I have 1 client in the IdentityServer set to hybrid flow. MVC uses cookies, while the API uses bearer tokens.
On my API HttpConfiguration I have set SuppressDefaultHostAuthentication. If I remove that line everything works, but then it uses cookies for the API which I don't want.
I use only HTTP and RequireSsl=false for now to avoid potential certificate problems.
I have tried for days to get this to work but I'm getting nowhere.
I don't even know how to debug this to get to cause of the 401.
By now I recognize just about every page that google suggests when I search for help.
Any ideas what it could be or how to debug this?
Here is my Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Trace()
.CreateLogger();
// MVC client
string authBaseAddress = "http://localhost:50319/identity";
string tokenEndpoint = authBaseAddress + "/connect/token";
string userInfoEndpoint = authBaseAddress + "/connect/userinfo";
string redirectUri = "http://localhost:50319/";
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "hybrid_clients",
Authority = authBaseAddress,
RedirectUri = redirectUri,
ResponseType = "code id_token token",
Scope = "openid profile roles sampleApi offline_access",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
},
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
// use the code to get the access and refresh token
var tokenClient = new TokenClient(
tokenEndpoint,
"hybrid_clients",
"secret");
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
// use the access token to retrieve claims from userinfo
var userInfoClient = new UserInfoClient(
new Uri(userInfoEndpoint),
tokenResponse.AccessToken);
var userInfoResponse = await userInfoClient.GetAsync();
// create new identity
var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));
n.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
n.AuthenticationTicket.Properties);
},
// Attach the id_token for the logout roundtrip to IdentityServer
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
}
}
});
AntiForgeryConfig.UniqueClaimTypeIdentifier = Constants.ClaimTypes.Subject;
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
// web api
app.Map("/api", a =>
{
var config = new HttpConfiguration();
a.UseCors(CorsOptions.AllowAll);
a.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
//AuthenticationMode = AuthenticationMode.Active,
Authority = authBaseAddress,
RequiredScopes = new[] { "sampleApi" },
DelayLoadMetadata = true
});
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
config.MapHttpAttributeRoutes();
a.UseWebApi(config);
});
// Identity server
app.Map("/identity", idsrvApp =>
{
idsrvApp.UseCors(CorsOptions.AllowAll);
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "Embedded IdentityServer",
SigningCertificate = LoadCertificate(),
Factory = new IdentityServerServiceFactory()
.UseInMemoryUsers(Users.Get())
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get()),
AuthenticationOptions = new IdentityServer3.Core.Configuration.AuthenticationOptions()
{
EnablePostSignOutAutoRedirect = true // Automatically redirects back to the client on signout
},
RequireSsl = false,
});
});
}
X509Certificate2 LoadCertificate()
{
return new X509Certificate2(
string.Format(#"{0}\bin\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test");
}
}
My client
new Client
{
Enabled = true,
ClientName = "Hybrid Clients",
ClientId = "hybrid_clients",
Flow = Flows.Hybrid,
//AllowAccessTokensViaBrowser = false,
RedirectUris = new List<string>
{
"http://localhost:50319/"
},
PostLogoutRedirectUris = new List<string>
{
"http://localhost:50319/"
},
AllowedScopes = new List<string>
{
"openid",
"profile",
"email",
"roles",
"address",
"all_claims",
"sampleApi",
"offline_access"
},
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
AccessTokenType = AccessTokenType.Reference,
LogoutSessionRequired = true
},
My scopes
public static class Scopes
{
public static IEnumerable<Scope> Get()
{
var scopes = new List<Scope>
{
StandardScopes.OpenId,
StandardScopes.Profile,
StandardScopes.Email,
StandardScopes.Address,
StandardScopes.OfflineAccess,
StandardScopes.RolesAlwaysInclude,
StandardScopes.AllClaims,
new Scope
{
Enabled = true,
Name = "roles",
Type = ScopeType.Identity,
Claims = new List<ScopeClaim>
{
new ScopeClaim("role")
}
},
new Scope
{
Enabled = true,
DisplayName = "Sample API",
Name = "sampleApi",
Description = "Access to a sample API",
Type = ScopeType.Resource
}
};
return scopes;
}
}
My API
[Authorize]
public class SecuredApiController : ApiController
{
public IHttpActionResult Get()
{
var user = User as ClaimsPrincipal;
var claims = from c in user.Claims
select new
{
type = c.Type,
value = c.Value
};
return Json(claims);
}
}
Part of my Razor view
<button data-bind="click:callApi">Call API</button>
<span data-bind="text:apiResult"></span>
<script>
$(function() {
ko.myViewModel = new ClientAppViewModel('#ViewData["access_token"]');
ko.applyBindings(ko.myViewModel);
});
</script>
My JavaScript (KnockoutJS) that calls SecuredApi
function ClientAppViewModel(accessToken) {
var self = this;
self.accessToken = accessToken;
self.apiResult = ko.observable('empty');
self.callApi = function () {
console.log('CallApi');
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:50319/api/SecuredApi");
xhr.onload = function () {
self.apiResult(JSON.stringify(JSON.parse(xhr.response), null, 2));
};
xhr.setRequestHeader("Authorization", "Bearer " + self.accessToken);
xhr.send();
}
}
The Katana logs for the API are what you want. With these you will see why the API is returning a 401.
You can access these using the Microsoft.Owin log source (see Katana Documentation)

WSO2 Identity Server with OpenId Connect

I am trying to use WSO2 Identity Server (5.1.0) with Asp.Net MVC, as a proof of concept i created a sample asp.net MVC project in visual studio 2015.
Following the WSO2 Guide, i have configured the identity server as required.
https://docs.wso2.com/display/IS510/OpenID+Connect+with+the+WSO2+Identity+Server+and+WSO2+OAuth2+Playground
On the sample application, i have added reference to Microsoft.Owin.Security.OpenIdConnect and added code to ConfigureAuth in Startup.Auth.cs file.
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.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
// 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.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
// Uncomment the following lines to enable logging in with third party login providers
//app.UseMicrosoftAccountAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseTwitterAuthentication(
// consumerKey: "",
// consumerSecret: "");
//app.UseFacebookAuthentication(
// appId: "",
// appSecret: "");
//app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
//{
// ClientId = "",
// ClientSecret = ""
//});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "SENmQQ9fOWcrqXjK1u3lXINhXtEa",
ClientSecret = "bFBJQqj4GT2Wfv8735fTTuHh3Isa",
Authority = "https://localhost:9443",
RedirectUri = "https://wso2openid.local.co.uk/Account/ExternalLoginCallback",
SignInAsAuthenticationType = "ClientCredCookie",
ResponseType = "id_token token",
Scope = "openid",
Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = "https://localhost:9443/oauth2/authorize",
TokenEndpoint = "https://localhost:9443/oauth2/token"
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = n =>
{
return Task.FromResult(0);
},
SecurityTokenReceived = n =>
{
return Task.FromResult(0);
},
AuthorizationCodeReceived = n =>
{
return Task.FromResult(0);
},
SecurityTokenValidated = n =>
{
var token = n.ProtocolMessage.AccessToken;
// persist access token in cookie
if (!string.IsNullOrEmpty(token))
{
n.AuthenticationTicket.Identity.AddClaim(
new Claim("access_token", token));
}
return Task.FromResult(0);
},
AuthenticationFailed = notification =>
{
if (string.Equals(notification.ProtocolMessage.Error, "access_denied", StringComparison.Ordinal))
{
notification.HandleResponse();
notification.Response.Redirect("/");
}
return Task.FromResult<object>(null);
}
}
});
}
When i run the application, on login it redirects to WSO2 Identity Server login and manage to login but when it redirect to Account\ExternalLoginCallback, the logininfo is always null.
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
Any advise as to why this is not working will be appreciated.?
NB: I tried to put a break point on SecurityTokenValidated but it did not hit that break point. The only breakpoint which i got hit was RedirectToIdentityProvider.
It's work well for me:
Step1 : Config WSO2 with this:
https://docs.wso2.com/display/IS570/Logging+in+to+a+.NET+application+using+the+Identity+Server
Step2:
public async Task<RedirectResult> LoginOAuth()
{
var url = "https://localhost:9443/oauth2/authorize?response_type=code&client_id=5a8urZQAc0r4R7iUS9ar1wOoq9Ma&scope=openid&redirect_uri=http://localhost:49545/Home/GetCode";
var client = new HttpClient();
var response = await client.GetAsync(url);
string urlDistance = response.RequestMessage.RequestUri.ToString();
client.Dispose();
return Redirect(urlDistance);
}
public async Task<RedirectToRouteResult> GetCode()
{
//باشد GetCode همشون حتما باید
var client = new HttpClient();
string code = Request.QueryString["code"];
string sessionState = Request.QueryString["session_state"];
string client_id = Request.QueryString["client_id"];
client.Dispose();
//از طریق ارسال کد میخواد توکن رو بگیره
//****************
var values = new Dictionary<string, string>
{
{ "code", code },
{ "sessionState", sessionState },
{ "client_id", "5a8urZQAc0r4R7iUS9ar1wOoq9Ma" },
{ "client_secret", "b0yefcCc4ftVYJm7ffQi2IZZ0eMa" },
{ "grant_type", "authorization_code" },
{ "redirect_uri", "http://localhost:49545/Home/GetCode" }//??????????????
};
var content = new FormUrlEncodedContent(values);
client = new HttpClient();
var response2 = await client.PostAsync("https://localhost:9443/oauth2/token", content);
string responseString = await response2.Content.ReadAsStringAsync();
JObject jsonResult = JObject.Parse(responseString);
string access_token = jsonResult["access_token"].ToString();
string refresh_token = jsonResult["refresh_token"].ToString();
string scope = jsonResult["scope"].ToString();
string id_token = jsonResult["id_token"].ToString();
string token_type = jsonResult["token_type"].ToString();
string expires_in = jsonResult["expires_in"].ToString();
//**************
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:9443/oauth2/userinfo?schema=openid");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", access_token);
string result = await httpClient.GetStringAsync("/oauth2/userinfo?schema=openid");
return RedirectToAction("Contact");
}

Resources