So we've set up an IDP (IdentityServer4, Core2) and have been using it for our own applications without problems (Implicit Flow). Now though, one of our partners will be using our IDP to make API requests from another application.
We've setup the ApiResources:
new ApiResource("api", "API",
new List<string>() {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Profile,
"role",
"team"
})
The client in question:
new Client {
ClientName= "ClientName",
Description = "ClientDescription",
LogoUri = "/img/ClientLogos/clientLogo.png",
ClientUri = "https://client.url",
RedirectUris = {
"https://...",
"https://...",
"http://...",
"http://..."
},
AllowedGrantTypes = GrantTypes.Code,
RequireConsent = true,
ClientId = "clientId",
AllowedScopes = { "api" },
ClientSecrets = { new Secret("clientSecret".Sha256()) },
AlwaysSendClientClaims = true,
AllowOfflineAccess = true,
Claims = {
...
}
}
I (wrongfully) assumed that since the client has the "api" scope, which in turn has the "OpenID" and "Profile" scope, the client would automatically gain authorization to use the UserInfo endpoint, but they are getting the "Forbidden" StatusCode.
Can someone explain to me what we're doing wrong here?
I think you need to include IdentityResources as well, because it dictates whats part of the ID-token and what is available from the UserInfo endpoint.
Related
Playing around with a demo project from PluralSight, I am trying to have the IDP redirect back to the server app on sign out.
The PostLogOutRedirectUris is defined in the config for the Client at the IDP level, but it doesn't seem to have any effect.
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "bethanyspieshophr",
ClientName = "Bethany's Pie Shop HRM",
AllowOfflineAccess = true,
AccessTokenLifetime = 120,
RequireConsent = false,
RequirePkce = true,
AllowedGrantTypes = GrantTypes.Code,
ClientSecrets = {
new Secret("108B7B4F-BEFC-4DD2-82E1-7F025F0F75D0".Sha256()) },
RedirectUris = { "https://localhost:44301/signin-oidc" },
PostLogoutRedirectUris = { "https://localhost:44301/signout-oidc" },
AllowedScopes = { "openid", "profile", "email", "bethanyspieshophrapi" }
}
};
If I manually at runtime set the LoggedOutViewModel it works as expected.
How are you performing the logout request? Remember that the value in the client settings is just the registered value which is verified during logout request. The user is not redirected automatically to the postLogoutRedirectUri. You have to pass a post_logout_redirect_uri parameter to the end session endpoint and this parameter must match on of the values in the PostLogoutRedirectUris setting. To use this feature you should also post a valid ID token in the id_token_hint parameter, so that the server knows which client is requesting the logout.
You can have a look at the end session enpoint docs for details.
The issue was simply due to a typo, which sent me on a wild goose chase.
PostLogoutRedirectUris = { "https://localhost:44301/signout-oidc" },
Should be
PostLogoutRedirectUris = { "https://localhost:44301/signout-callback-oidc" },
And then, it worked.
I am getting Invalid authorization code{"code": "MyTestCode"},
Here is more detailed error:
Invalid authorization code{"code": "MyTestCode"}, details: {"ClientId": "AuthorizationCodeClientFlow", "ClientName": "Authorization Code Client", "GrantType": "authorization_code", "Scopes": null, "AuthorizationCode": "MyTestCode", "RefreshToken": null, "UserName": null, "AuthenticationContextReferenceClasses": null, "Tenant": null, "IdP": null, "Raw": {"grant_type": "authorization_code", "code": "MyTestCode", "redirect_uri": "https://localhost:5000/oauth/callback", "client_id": "AuthorizationCodeClientFlow"}, "$type": "TokenRequestValidationLog"} <s:IdentityServer4.Validation.TokenRequestValidator>
I am testing using Postman
This is client generated from this code :
{
ClientName = "Authorization Code Client",
ClientId = "AuthorizationCodeClientFlow",
AllowedGrantTypes = GrantTypes.Code,
ClientSecrets =
{
new Secret("AuthorizationCodeClientFlowSecret".Sha512())
},
AllowedScopes =
{
"all"
},
RedirectUris =
new List<string> {
"https://localhost:5000/oauth/callback"
},
AllowOfflineAccess = false,
AccessTokenLifetime = 60
};
https://localhost:5105/oauth/authorize works fine. I get error in https://localhost:5105/oauth/token step. When I validate request like that:
var form = (await _httpContextAccessor.HttpContext.Request.ReadFormAsync()).AsNameValueCollection();
var validationResult = await _requestValidator.ValidateRequestAsync(form, clientResult);
if (validationResult.IsError)
{
return new IdpTokenResponse
{
Custom = new Dictionary<string, object>
{
{ "Error", validationResult.Error },
{ "ErrorDescription", validationResult.ErrorDescription }
}
};
}
You need to take the authorization code that you receive from the initial authentication request and then take it and pass it along when you get the token from the token endpoint.
one unrelated thing is that you should always ask for the openid scope when you authenticate against IdentityServer, All or "" is not valid.
I have been trying to connect an assistant action to my backend server
I am using my own Oauth server and followed the instructions on
https://developers.google.com/actions/identity/oauth2?oauth=code
I am using actions_intent_Sign_in for my dialogflow event intent (like https://actions-on-google.github.io/actions-on-google-nodejs/classes/conversation_helper.signin.html)
when i use my action to sign in, i get the login window to my server, i do the account linking and i can see that i generated the tokens on my server but i cant find the token in (conv.user.access.token)
and this is the code for my intent using "actions on google sdk "
'use strict';
var _ = require('lodash');
var path = require('path')
var express = require('express')
var http = require('http')
const bodyParser = require('body-parser');
var expressApp = express().use(bodyParser.json());
var server = http.createServer(expressApp).listen(3000)
const {
dialogflow,
SignIn
} = require('actions-on-google');
const app = dialogflow({
debug: true,
clientId: '7b4a6dfc-4b35-11e9-8646-d663bd873d93'
});
app.intent('Start Sign-in', conv => {
conv.ask(new SignIn());
});
app.intent('Get Sign-in', (conv, params, signin) => {
console.log("get sign in ");
console.log(JSON.stringify(signin));
if (signin.status === 'OK') {
const access = conv.user.access.token
console.log("the access token is " + access);
conv.ask('Great, thanks for signing in! What do you want to do next?');
} else {
conv.ask('I wont be able to save your data, but what do you want to do next?.');
}
});
and the response comes back as
{"#type":"type.googleapis.com/google.actions.v2.SignInValue","status":"OK"}
the access token is undefined
Response {
"status": 200,
"headers": {
"content-type": "application/json;charset=utf-8"
},
"body": {
"payload": {
"google": {
"expectUserResponse": true,
"richResponse": {
"items": [
{
"simpleResponse": {
"textToSpeech": "Great, thanks for signing in! What do you want to do next?"
}
}
]
}
}
}
}
}
the user object of conv has only this data
"user": {
"raw": {
"lastSeen": "2019-03-20T12:46:23Z",
"locale": "en-US",
"userId": "okdhyeGSk5tofgLjEepIUrA6mmewCESY8MjklZRPvQJgv6-uybfPobwdfgtrGZJ3bE2sM9ninhst"
},
"storage": {},
"_id": "okdhyeGSk5tofgLjEepIUrA6mmewCESY8MjklZRPvQJgv6-uybfPobwdfgtrGZJ3bE2sM9ninhst",
"locale": "en-US",
"permissions": [],
"last": {
"seen": "2019-03-20T12:46:23.000Z"
},
"name": {},
"entitlements": [],
"access": {},
"profile": {}
}
i dont know where the access/refresh token can be found or if there is any requirement for the post to send from my oauth server that i missed
so finally i managed to get it working with the help of Actions on Google Support Team
the problem was me having another google account logged-in in another tab, even though i had the AoG and dialogflow agent connected with the same account
tried all using incognito window and it works
I am configuring a asp.net mvc app (or relying party) to use thinktecture identity server. Identity Server is up and running locally and I am able to retrieve metadata from its endpoint.
Here is the code used to register the middleware:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions("localIdp")
{
AuthenticationType = "localIdp",
ClientId = "Welfare4Partners",
MetadataAddress = "https://localhost:44333/core/.well-known/openid-configuration",
//Configuration = new OpenIdConnectConfiguration
//{
// AuthorizationEndpoint = "https://localhost:44333/core/connect/authorize",
// JwksUri = "https://localhost:44333/core/.well-known/jwks",
// TokenEndpoint = "https://localhost:44333/core/connect/token",
// UserInfoEndpoint = "https://localhost:44333/core/connect/userinfo",
// Issuer = "https://localhost:44333/core",
// EndSessionEndpoint = "https://localhost:44333/core/connect/endsession",
//},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = (context) =>
{
return Task.FromResult(context);
},
SecurityTokenReceived = (context) =>
{
return Task.FromResult(context);
},
SecurityTokenValidated = (context) =>
{
return Task.FromResult(context);
},
AuthenticationFailed = (context) =>
{
context.HandleResponse();
context.OwinContext.Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType, context.Options.AuthenticationType);
context.SkipToNextMiddleware();
return Task.FromResult(context);
},
MessageReceived = (context) =>
{
return Task.FromResult(context);
},
RedirectToIdentityProvider = (context) =>
{
return Task.FromResult(context);
}
},
Authority = "https://localhost:44333",
RedirectUri = AppSettings.PostLoginRedirectUri,
ResponseType = OpenIdConnectResponseTypes.IdToken,
Scope = "openid",
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType
});
As you can see I have commented the Configuration property once I have the MetadataAddress prop set.
I am calling the middleware with the following line of code in an Action:
var authProperties = new AuthenticationProperties { RedirectUri = AppSettings.PostLoginRedirectUri, IsPersistent = false, };
OwinContext.Authentication.Challenge(authProperties, authenticationType);
I have verified the value of the authenticationType and it contains "localIdp". After calling the challenge nothing happens. Odd think is that if I comment the metadataAddress and uncomment the Configuration property, the middleware is called.
Is there a way to debug the OWIN requests in order to check what's wrong in the code?
Metadata is the following:
{
"issuer": "https://localhost:44333/core",
"jwks_uri": "https://localhost:44333/core/.well-known/jwks",
"authorization_endpoint": "https://localhost:44333/core/connect/authorize",
"token_endpoint": "https://localhost:44333/core/connect/token",
"userinfo_endpoint": "https://localhost:44333/core/connect/userinfo",
"end_session_endpoint": "https://localhost:44333/core/connect/endsession",
"check_session_iframe": "https://localhost:44333/core/connect/checksession",
"revocation_endpoint": "https://localhost:44333/core/connect/revocation",
"introspection_endpoint": "https://localhost:44333/core/connect/introspect",
"frontchannel_logout_supported": true,
"frontchannel_logout_session_supported": true,
"scopes_supported": ["openid", "profile", "email", "address", "roles", "all_claims", "offline_access", "read", "write"],
"claims_supported": ["sub", "name", "family_name", "given_name", "middle_name", "nickname", "preferred_username", "profile", "picture", "website", "gender", "birthdate", "zoneinfo", "locale", "updated_at", "email", "email_verified", "address", "role"],
"response_types_supported": ["code", "token", "id_token", "id_token token", "code id_token", "code token", "code id_token token"],
"response_modes_supported": ["form_post", "query", "fragment"],
"grant_types_supported": ["authorization_code", "client_credentials", "password", "refresh_token", "implicit", "custom2", "custom"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"code_challenge_methods_supported": ["plain", "S256"],
"token_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic"]
}
I'm tying to figure out OAuth2.0, OIDC1.0 and IdentityServer4. I've setup a test MVC Core client with only "openid" scope requested. But somehow OpenIdConnnect middleware keeps adding "profile" scope to the requested scopes. Is "profile" a mandatory scope? Should I enable it? Or what am I doing wrong here? I'd appreciate any input.
IdSrv resources:
_identityResources = new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResource
{
Name = "test_user",
UserClaims = new[] { "test_user.email" }
}
};
_apiResources = new List<ApiResource>
{
new ApiResource
{
Name = "test_api",
Scopes =
{
new Scope()
{
Name = "test_api.account.create",
UserClaims = new[] { "test_api.account.create" }
}
}
}
};
IdSrv client config:
new Client
{
ClientId = "client.mvcx",
ClientName = "MVC Core Client",
AllowedGrantTypes = GrantTypes.Hybrid,
AllowAccessTokensViaBrowser = false,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { Common.Addresses.Client + "/signin-oidc" },
PostLogoutRedirectUris = { Common.Addresses.Client },
LogoutUri = Common.Addresses.Client + "/signout-oidc",
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId
},
AllowOfflineAccess = false,
RequireConsent = false,
AlwaysIncludeUserClaimsInIdToken = true
},
MVC Client:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "cookies",
AutomaticAuthenticate = true,
ExpireTimeSpan = TimeSpan.FromMinutes(60)
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "cookies",
Authority = Common.Addresses.IdSrv,
RequireHttpsMetadata = false,
ClientId = "client.mvcx",
ClientSecret = "secret",
ResponseType = "code id_token",
Scope = { "openid" },
SaveTokens = true,
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
NameClaimType = IdentityModel.JwtClaimTypes.Name,
RoleClaimType = IdentityModel.JwtClaimTypes.Role,
},
IdSrv error:
info: IdentityServer4.Hosting.IdentityServerMiddleware[0]
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.AuthorizeEndpoint for /connect/authorize
fail: IdentityServer4.Validation.ScopeValidator[0]
Invalid scope: profile
fail: IdentityServer4.Endpoints.AuthorizeEndpoint[0]
Request validation failed
info: IdentityServer4.Endpoints.AuthorizeEndpoint[0]
{
"ClientId": "client.mvcx",
"ClientName": "MVC Core Client",
"RedirectUri": "http://localhost:32579/signin-oidc",
"AllowedRedirectUris": [
"http://localhost:32579/signin-oidc"
],
"SubjectId": "anonymous",
"ResponseType": "code id_token",
"ResponseMode": "form_post",
"GrantType": "hybrid",
"RequestedScopes": "openid profile",
...
The OpenIdConnectionOptions automatically requests the openid and profile scopes (see source code), with a private setter on the Scope property.
When you set scopes like you are, you are not setting a new list, but adding to the existing.
Clearing and then adding the scope works:
var options = new OpenIdConnectOptions();
options.Scope.Clear();
options.Scope.Add("openid");
app.UseOpenIdConnectAuthentication(options);