I am having trouble getting an JWT from IdentityServer3. I am using aurelia with aurelia-auth.
The error I am getting from IdentityServer is
"The client application is not known or is not authorized."
Was wondering what I am missing in the configuration? Configuration is below
//Server Clients
public static class Clients
{
public static IEnumerable<Client> Get()
{
return new List<Client> {
new Client {
ClientName = "AureliaApplication",
Enabled = true,
ClientId = "aureliaClient",
AllowAccessToAllScopes = true,
Flow = Flows.ResourceOwner,
AccessTokenType = AccessTokenType.Jwt,
AccessTokenLifetime = 3600
}
};
}
}
//Aurelia-Auth Provider Config
var config = {
providers: {
IdentityServerV3: {
name:'IdentityServerV3',
url: '/auth/IdentityServerV3',
authorizationEndpoint: 'https://localhost:44300/core/connect/authorize',
redirectUri: window.location.origin || window.location.protocol + '//' + window.location.host,
scope: ['openid'],
scopePrefix: 'openid',
scopeDelimiter: '&',
display: 'popup',
type: '2.0',
clientId: 'aureliaClient',
popupOptions: { width: 1020, height: 618 }
}
}
}
export default config;
You need to configure the scope of the client in the IdentityServer
new Client
{
ClientId = "Aurelia Client",
ClientName = "aureliaClient",
ClientSecrets = new List<Secret> {
new Secret(Constants.IdentitySecret.Sha256())
},
Flow = Flows.Hybrid,
RequireConsent = true,
AllowRememberConsent = true,
RedirectUris = new List<string> {
"http://localhost:9000"
},
PostLogoutRedirectUris = new List<string> {
"http://localhost:9000"
},
AllowedScopes = new List<string> {
Constants.StandardScopes.OpenId,
Constants.StandardScopes.Profile,
Constants.StandardScopes.Roles,
"apiAccess"
}
}
The Aurelia config has to have to correct url to the different IdentityServer endpoint. These endpoints can usually be found in the openid-configuration of the server (in this example it would be : https://localhost:44301/core/.well-known/openid-configuration).
The same scope as define in the client configuration in the IdentityServer
var config = {
baseUrl : 'https://localhost:44301/core',
tokenName : 'id_token',
profileUrl: '/connect/userinfo',
unlinkUrl : '/connect/endsession',
logoutRedirect: '/',
loginRedirect : '#/',
providers : {
identSrv : {
name: 'identSrv',
url: '/connect/token',
authorizationEndpoint: 'https://localhost:44301/core/connect/authorize/',
redirectUri: window.location.origin || window.location.protocol + '//' + window.location.host,
scope: ['profile', 'apiAccess','openid', 'roles'],
responseType :'code id_token token',
scopePrefix: '',
scopeDelimiter: ' ',
requiredUrlParams: ['scope', 'nonce'],
optionalUrlParams: ['display'],
state: 'session_state',
display: 'popup',
type: '2.0',
clientId: 'jsClient',
flow: 'hybrid',
nonce : function(){
var val = ((Date.now() + Math.random()) * Math.random()).toString().replace(".", "");
return encodeURIComponent(val);
},
popupOptions: { width: 452, height: 633 }
}
}
Scott actually found the solution (I'm just using it to answer) and you can find an example on his github https://github.com/devscott/identityServer3Example
Related
###I am using identity server 4 for authentication for .net and angular apps.
if I log out from one client it does not log out from others.###
how can I delete the user session and implement single-signout for all clients
#the config class in identity server#
//MVC Client
new Client
{
ClientName = ".NET 4 MVC website",
ClientId = "net4mvcclient",
ClientSecrets =
{
new Secret("secret3".Sha256())
},
//Grant types are a way to specify how a client wants to interact with IdentityServer
AllowedGrantTypes = GrantTypes.Implicit,
RequireConsent = false,
AllowOfflineAccess = true,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{_config.GetValue<string>("IdentityServerSettings:MVCBaseUri")}/signin-oidc" },
PostLogoutRedirectUris = { $"{_config.GetValue<string>("IdentityServerSettings:MVCBaseUri")}/" },
AllowedScopes = {"openid", "profile", "offline_access", "api1", "api2" } ,
AllowedCorsOrigins = {$"{_config.GetValue<string>("IdentityServerSettings:MVCBaseUri")}"},
AccessTokenLifetime = 50000
},
//angular_spa
new Client {
RequireConsent = false,
ClientId = "angular_spa",
ClientName = "Angular SPA",
AllowedGrantTypes = GrantTypes.Implicit,
AllowedScopes = { "openid", "profile", "offline_access", "api1", "api2" },
RedirectUris = { $"{_config.GetValue<string>("IdentityServerSettings:AngularBaseUri")}/#auth-callback/#" },
PostLogoutRedirectUris = { $"{_config.GetValue<string>("IdentityServerSettings:AngularBaseUri")}/" },
AllowedCorsOrigins = { $"{_config.GetValue<string>("IdentityServerSettings:AngularBaseUri")}" },
AllowAccessTokensViaBrowser = true,
AccessTokenLifetime = 3600
}
};
startup class in MVC client
public void ConfigureIdentityServer(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
Authority = ConfigurationManager.AppSettings["IdentityServerUrl"], //Identity server Url
ClientId = "net4mvcclient",
ClientSecret = "secret3",
RedirectUri = ConfigurationManager.AppSettings["HourlyMVCUrl"] +"/signin-oidc", //Net4MvcClient's URL
PostLogoutRedirectUri = ConfigurationManager.AppSettings["HourlyMVCUrl"]+"/", //MVC Client URL
ResponseType = "id_token token",
RequireHttpsMetadata = false,
Scope = "openid profile api1 api2 offline_access",
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
NameClaimType = "name"
},
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = n =>
{
n.AuthenticationTicket.Identity.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
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);
}
}
});
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
app.UseNLog((eventType) => LogLevel.Debug);
}
#the logout function in identity server#
public async Task<IActionResult> Logout(LogoutInputModel model)
{
// build a model so the logged out page knows what to display
var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);
if (User?.Identity.IsAuthenticated == true)
{
// delete local authentication cookie
// Request.GetOwinContext().Authentication.SignOut();
await HttpContext.SignOutAsync();
await _signInManager.SignOutAsync();
// raise the logout event
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
}
// check if we need to trigger sign-out at an upstream identity provider
if (vm.TriggerExternalSignout)
{
// build a return URL so the upstream provider will redirect back
// to us after the user has logged out. this allows us to then
// complete our single sign-out processing.
string url = Url.Action("Logout", new { logoutId = vm.LogoutId });
// this triggers a redirect to the external provider for sign-out
return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
}
if (vm.PostLogoutRedirectUri != null)
{
return SignOut(new AuthenticationProperties { RedirectUri = vm.PostLogoutRedirectUri }, vm.ExternalAuthenticationScheme);
}
return View("LoggedOut", vm);
}
first of all . i created a gRpc service project from VS2019, and added Jwt Bearer Authentication and Authorization.
when client app (VS code + Vue.js) request login service method , a token will be sent.
string key = "AB6B4DC1-FABF-47AE-A585-4AD154758B05";
var securityKey = new SymmetricSecurityKey(Guid.Parse(key).ToByteArray());
var claims = new[] {
new Claim(ClaimTypes.Name, clientId),
new Claim(ClaimTypes.NameIdentifier,clientId)
};
var token = new JwtSecurityToken("client",
"client",
claims,
notBefore:DateTime.Now,
expires: DateTime.Now.AddSeconds(60),
signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256)
);
return new JwtSecurityTokenHandler().WriteToken(token);
and then jwt configuration is
var key = "AB6B4DC1-FABF-47AE-A585-4AD154758B05";
options.TokenValidationParameters = new TokenValidationParameters
{
ClockSkew = TimeSpan.FromSeconds(30),
ValidateIssuer = true,
ValidateAudience = true,
AudienceValidator = (m, n, z) =>
{
return m != null && m.FirstOrDefault().Equals("");
},
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidAudience = "client",
ValidIssuer = "client",
IssuerSigningKey = new SymmetricSecurityKey(Guid.Parse(key).ToByteArray())
};
the problem is i can't pass authorization as the header item to gRpc Server
code bellow:
'
import { Metadata } from '#grpc/grpc-js/build/src/metadata'
import { CallCredentials } from '#grpc/grpc-js/build/src/call-credentials'
export default {
name: 'App',
created () {
this.client = new GreeterClient('https://localhost:5001', CallCredentials.createFromPlugin, null)
this.appClient = new AppClient('https://localhost:5001', null, null)
},
methods: {
loginSys () {
var lm = new LoginModel()
lm.setLoginname('admin')
lm.setLoginpwd('abc123!##')
lm.setLogincode('kkkkkrrr')
var appInfo = new AppInfo()
appInfo.setAppid('asdfasdfasdf')
appInfo.setApptoken('12345678901234567890')
appInfo.setModel(lm)
this.appClient.login(appInfo, {}, (err, response) => {
debugger
if (err) {
console.log(`Unexpected error for sayHello: code = ${err.code}, message = "${err.message}"`)
} else {
console.log(response.getMessage())
console.log(response.getIssucceed())
if (response.hasUserinfo()) {
var userInfo = response.getUserinfo()
console.log(userInfo.getAvatar())
console.log(userInfo.getUsername())
console.log(userInfo.getCellphone())
}
}
})
},
get1 () {
var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiY2xpZW50SWQiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6ImNsaWVudElkIiwibmJmIjoxNjI5MDM0MzkxLCJleHAiOjE2MjkwMzQ0NTEsImlzcyI6IkFpMi5XZWIiLCJhdWQiOiJBaTIuV2ViIn0.1QpFsvzRvlBvTPNQ4hzETIDaLfmUsxmVvXaRyXVskjI'
var metadata = new Metadata()
metadata.add('authorization', 'Bearer ' + token)
var request = new HelloRequest()
request.setName('World')
this.client.sayHello(request, metadata, (err, response) => {
if (err) {
console.log(`Unexpected error for sayHello: code = ${err.code}, message = "${err.message}"`)
} else {
console.log(response.getMessage())
}
})
}
}
}
method : get1 is used to do the "authorization" test .
when i executed this method , i found authorization doesn't exist in http headers.
i don't know why , and what should i do ?
i got the answer.
const token = 'eyJhbGciOiJIUzyXVskjI'
const metadata = { 'authorization': 'Bearer ' + token }
Everything seems normal but it is not working, It returns "Invalid_Client" - (400 - Bad request).
Both side so simple below;
Identity Server Code:
new Client
{
ClientId = "js",
ClientSecrets = {
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.ClientCredentials,
RequireClientSecret = false,
AllowedScopes =
{
"api1"
}
}
Javascript Client Code:
axios.post('http://localhost:5000/connect/token',request, {
headers: {
'client_id' : 'js',
'client_secret' : 'secret',
'grant_type': 'client_credentials',
'scope' : 'api1'
}});
The parameters should be passed in request body not request header , you can modify the client script as :
const params = new URLSearchParams();
params.append('client_id', 'js');
params.append('client_secret', 'secret');
params.append('grant_type', 'client_credentials');
params.append('scope', 'api1');
axios.post('http://localhost:5000/connect/token', params, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
}).then(function (response) {
console.log(response.data);
});
And also configure CORS is to use the AllowedCorsOrigins collection on the client configuration:
new Client
{
ClientId = "js",
ClientSecrets = {
new Secret("secret".Sha256())
},
AllowedCorsOrigins= new List<string>() { "http://localhost:5002" },
AllowedGrantTypes = GrantTypes.ClientCredentials,
RequireClientSecret = false,
AllowedScopes =
{
"api1"
}
},
Should not use normall fetch or axis.
Because the service is Oauth service and should be use 'client-oauth2' library.
Code Example:
var ClientOAuth2 = require("client-oauth2");
var authRequest = new ClientOAuth2({
clientId: IDENTITY_CONFIG.client_id,
clientSecret: IDENTITY_CONFIG.client_secret,
accessTokenUri: IDENTITY_CONFIG.token_endpoint,
scopes: [IDENTITY_CONFIG.grantType]
});
return authRequest.credentials.getToken();
I am trying to use the Expo AppAuth module to do authentication using IdentityServer4 in react native. Cant seem to get the redirectUri settings right. I'm getting an 'invalid redirect uri" error when i redirect to identityServer.
This is my client on identityserver
return new List<Client>
{
new Client
{
ClientName = "client",
ClientId = "client",
RequirePkce = true,
AllowedGrantTypes = GrantTypes.Code,
RequireClientSecret = false,
RequireConsent = true,
RedirectUris =
{
"host.exp.Exponent" //Is this correct
},
AllowOfflineAccess = true,
RefreshTokenUsage = TokenUsage.ReUse,
AllowedScopes = { "openid", "profile"},
}
};
My config settings for AppAuth are
const config = {
issuer: 'http://localhost:3000',
clientId: 'client',
scopes: ['profile', 'openid'],
redirectUri: "host.exp.Exponent"
}
You should specify redirectUri as the address value.
AppAuth Definitions:
async function _executeAsync(props: OAuthProps): Promise<TokenResponse> {
if (!props.redirectUrl) {
props.redirectUrl = getDefaultOAuthRedirect();
}
assertValidProps(props);
return await ExpoAppAuth.executeAsync(props);
}
export function getDefaultOAuthRedirect(): string {
return `${ExpoAppAuth.OAuthRedirect}:/oauthredirect`;
}
In my profile service why is RequestedClaimTypes Empty? I am expecting the profile claims to be requested. And per this they should contain FamilyName and Given Name claim types.
GetIdentityResources
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
}
Client
new Client
{
ClientId = "46a0ab4a-1321-4d77-abe5-98f09310df0b",
ClientName = "TypeScript SPA client",
RequireClientSecret = false, // if false this is a public client.
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "http://localhost:3000/callback" },
PostLogoutRedirectUris = { "http://localhost:3000/" },
AllowedCorsOrigins = { "http://localhost:3000" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
RequireConsent = false,
},
oidc-client configuration typescript
const myOidcClientSettings: OidcClientSettings = {
authority: `${protocol}//${hostname}:5000`,
client_id: '46a0ab4a-1321-4d77-abe5-98f09310df0b',
post_logout_redirect_uri: `${protocol}//${hostname}${port ? `:${port}` : ''}/`,
redirect_uri: `${protocol}//${hostname}${port ? `:${port}` : ''}/callback`,
response_type: 'id_token token',
scope: 'openid profile'
};
const myUserManagerSettings: UserManagerSettings = {
...myOidcClientSettings,
automaticSilentRenew: false,
filterProtocolClaims: true,
loadUserInfo: true,
monitorSession: false,
silent_redirect_uri: `${protocol}//${hostname}${port ? `:${port}` : ''}/callback`,
};
Inside the Login Post I add the following claims:
Claim[] claims =
{
new Claim(JwtClaimTypes.Name, $"{loginResponse.FirstName} {loginResponse.LastName}"),
new Claim(JwtClaimTypes.Email, loginResponse.EmailAddress),
new Claim(JwtClaimTypes.PhoneNumber, loginResponse.PhoneNumber),
new Claim(JwtClaimTypes.FamilyName, loginResponse.LastName),
new Claim(JwtClaimTypes.GivenName, loginResponse.FirstName),
//new Claim(JwtClaimTypes.AuthorizationCodeHash, aRequest.Password), // The Password will be need by the BFF but can NOT be sent to the Typescript client
};
await HttpContext.Authentication.SignInAsync(subjectId, userName, authenticationProperties, claims);
ProfileService
public Task GetProfileDataAsync(ProfileDataRequestContext aProfileDataRequestContext)
{
Logger.LogDebug("Get profile called for {subject} from {client} with {claimTypes} because {caller}",
aProfileDataRequestContext.Subject.GetSubjectId(),
aProfileDataRequestContext.Client.ClientName,
aProfileDataRequestContext.RequestedClaimTypes,
aProfileDataRequestContext.Caller);
if (aProfileDataRequestContext.RequestedClaimTypes.Any())
{
aProfileDataRequestContext.AddFilteredClaims(aProfileDataRequestContext.Subject.Claims);
}
return Task.FromResult(0);
}
Resulting User Info that profile does NOT contain the profile items: (Shortend for readability
"User info": {
"id_token": "eyJhbGciOiJSUzI1N",
"session_state": "M5uV9nYzvmlWjvpjmX--OOPcwAEeVesV7aG9ZO0svS8.8f757e9a033183149734adb156fbb39d",
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6",
"token_type": "Bearer",
"scope": "openid profile",
"profile": {
"sid": "4372a4cbb9938449a39d72db1a9fc6f0",
"sub": "TestDemo12#gmail.com",
"auth_time": 1505037917,
"idp": "local",
"amr": [
"pwd"
]
},
"expires_at": 1505042091,
"state": {
"returnUrl": "/en-us/test"
}
}
It looks like you have to include the following option to your client,
AlwaysIncludeUserClaimsInIdToken = true
So that your client will include the claims in the token.