So I have a .Net Core web api, lets call it "CMS" and its currently protected by an IdentityServer4 server as an api resource. I have configured the ID4 server to have the IDP Claim of MyIDP.
For business reasons, I need to give a client their own IdentityServer but they would also like to have their users access the same api "CMS" .
Is this possible?
In the StartUp.cs of my CMS api it currently looks like this
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://www.idserver1.com";
options.RequireHttpsMetadata = true;
options.ApiName = "cmsapi";
});
so to add protection for another id server I assume i could just duplicate the AddAuthentication but change the scheme name from Bearer to something else but that seems wrong?
The reason I think this should be possible because I have been able to add multiple external providers to my Web Application in this manner . But this is for s sign in flow and not for an api.
If this is possible how do I go about this?
This can be achieved quite simply. Suppose you want to issue a separate subdomain for each of your clients: auth0.yourdomain.com, auth1.yourdomain.com and you want an api resource to respect the token from either of those identity providers.
Assuming that the signing key is the same, you can configure a shared issuer uri on the identity server side in Startup.cs->ConfigureServices(...):
var builder = services.AddIdentityServer(options => {
options.IssuerUri = "auth.yourdomain.com";
})
...
And then on the api side you can respect the single issuer uri without having to duplicate authentication schemes:
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "auth.yourdomain.com";
options.RequireHttpsMetadata = true;
options.ApiName = "cmsapi";
});
One thing I can't remember is if the request scheme (http/https) is inferred for the issuer uri or not so you might need to specify that as well (https:\\auth.yourdomain.com). Other than that, this sort of implementation should be quite seamless as far as your clients are concerned.
i think i may have figured out the solution, based off another problem that was happening to me over here
Using Client Credentials flow on identityserver4 and custom AuthorizationHandler User.Identity.isAuthenticated = false
turns out you can use multiple authenticationschemes to protect an api and choose which things to protect with what using the authenticationSchemes property of the Authorize Attribute.
so you would just need a way to map the incoming bearer token to the correct authentication scheme
Related
I've come across an example of co-hosting Identity Server 4 on the same App Host as the API Services that need it for authentication and authorization.
Now I was able to replicate this successfully by just pure authentication but when it comes to authorization using Roles I couldn't get it to work, i.e. adding the [Authorize(Roles = "My Role")] attribute on my Controller Action.
The access token contains the "role" scope and claim but it doesn't seem to be respected at all.
I initially tried the code below but it doesn't execute the JWT Bearer bit at all which leads me to believe that Identity Server uses its own handler for that purpose and I have no idea how to configure it if at all.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
// JWT tokens (default scheme)
.AddJwtBearer(options =>
{
options.Authority = "https://localhost:44367/";
options.Audience = "API";
options.TokenValidationParameters.RoleClaimType = "role";
});
Then I came across this line from the example code (I mentioned initially in this post) which seems like its supposed to grant me the ability to run API services along side with Identity Server:
services.AddLocalApiAuthentication();
But it also doesn't seem to do what I want.
So does the Identity Server authentication middleware allow me to accomplish role-based authentication or is there some other mechanism (i.e. Policies) that I need to look into?
Something worth noting, I was able to accomplish all of this successfully with Identity Server 4 but by hosting it all separately. I want to see what it takes to host it all together.
Just rechecked the example and it worked perfectly fine.
When you use
services.AddLocalApiAuthentication();
it sets up the IdentityServerAccessToken authenticationScheme.
To use it in your API controller you type
[Authorize(IdentityServerConstants.LocalApi.PolicyName)]
as described in the doc, or just
[Authorize(AuthenticationSchemes = "IdentityServerAccessToken")]
All you need to check the roles is one more argument for the attribute:
[Authorize(AuthenticationSchemes = "IdentityServerAccessToken", Roles = "test role1")]
In a angularjs application, i use adal and adal-angular libraries to authentify user on Azure AD. On backend I use OWIN middleware in my webAPI to add custom claims with :
app.UseWindowsAzureActiveDirectoryBearerAuthentication(new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Audience = ConfigurationManager.AppSettings["ida:Audience"],
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
Provider = new OAuthBearerAuthenticationProvider()
{
OnValidateIdentity = async context =>
{
if(!context.IsValidated)
return;
var userManager = context.OwinContext.Get<UserManager>();
string email = context.Ticket.Identity.GetClaimValue(System.Security.Claims.ClaimTypes.Email);
User user = userManager.GetByEmail(email);
context.Ticket.Identity.AddClaim(new Claim(ClaimTypes.UserId, user.Id.ToString(CultureInfo.InvariantCulture));
}
}
});
It's work for the current request, but how to add the claim in the bearer send by httpinterceptor of adal-angular for the next requests?
To answer your direct question: you cannot modify the access token, period.
It has a digital signature which will no longer be valid if you change anything in the token.
It's a security measure that prevents tampering.
Your OWIN middleware downloads the public keys of the signing key pairs from Azure AD on startup, and uses those to validate tokens.
Now if you stored your internal ids as extension attributes,
it would actually be possible to tell Azure AD to include that in the tokens.
This feature is in preview though, and not recommended for production use: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims#configuring-custom-claims-via-directory-extensions.
Here you would set the internal id as an extension on the User entity, and have it included for access tokens to your API.
A pragmatic solution to your issue might be an in-memory cache.
How often do emails change?
I am confused about how this is used.
Most examples I've seen have it given as "/signout-callback-oidc". That seems to indicate that it uses OIDC middleware in the process. What if I want to return to a specific client page?
The automatic redirect isn't working when I set IdentityServer's AccountOptions.cs property of AutomaticRedirectAfterSignOut to true. Further, during logout, I do not receive the client's PostLogoutRedirectUri.
So, is that link supposed to go to the OIDC middleware, or is it available for use to redirect to the client?
Your client has to be configured to request the callback to one of those URIs as part of the client-initiated sign-out flow.
IS4 clients can be configured with lists of allowable redirect URIs for both sign-in and sign-out, which I'm guessing is where you see /signout-callback-oidc -- if I remember right, either the docs or maybe the Quickstart code uses that, but there's nothing special about that particular URI name. (It isn't some OIDC standard, or a "well-known" name, or anything of that nature, as far as I know.)
The missing piece of the puzzle is to configure OIDC in the client application. You didn't mention what kind of application is on the client side, but in ASP.NET Core it's an option named SignedOutCallbackPath on the AddOpenIdConnect service:
services.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = appConfig["OidcAuthority"];
options.ClientId = appConfig["OidcClientId"];
options.ClientSecret = appConfig["OidcClientSecret"];
// etc
options.SignedOutCallbackPath = "/jake-says-goodbye";
});
This causes the OIDC implementation to add a property to the sign-out request identifying that redirect URI. As long as your application properly identifies itself, as briefly mentioned in the docs here, and as long as /jake-says-goodbye is one of the approved post-logout redirect URIs on the IS4 side, you should get the callback you're expecting.
(I specifically mention "proper" identification because, based on github questions I've seen, it sounds like it might be more difficult to manage for a JS-based SPA client app versus whatever helpful things MVC does behind the scenes to manage server-to-server OIDC interaction. I can't speak to that as I've not had a need to implement any SPA clients with IS4 yet.)
The problem is that you have to set a very specific parameter in order for the PostLogoutRedirectUri to not show up as null on IdentityServer's side, and testing any of the options results in having to step through a ton of ways to set it, most of them still resulting in null. Since I'm using an older client with IdentityServer4 (in order to enable .NET 4.x webapps to authenticate through IdentityServer4, cannot easily use .NET Core with those projects - luckily IdentityServer4 is still compatible with the older client code), the action that triggers signout has two relevant things (and you'll find a TON of examples of code for that will not work for you with MVC in .NET 4.x):
Use the signout() method in this sample github repo (the IdentityServer3 MVC Owin sample client): https://github.com/IdentityServer/IdentityServer3.Samples/blob/master/source/Clients/MVC%20OWIN%20Client/Controllers/HomeController.cs You can trigger that action from a button in a view.
That will get intercepted by the client's Owin middleware if you do this: https://github.com/IdentityServer/IdentityServer3/issues/2687#issuecomment-194739035 I didn't use the stored message bit, and I added the PostLogoutRedirectUri parameter in a way that IdentityServer4's LogoutRequest model wouldn't remove with this line in the same segment:
n.ProtocolMessage.PostLogoutRedirectUri = "http://myredirectaddress/ActionToDoOnceReturned";
You have to make sure that the above matches the client's PostLogoutRedirectUri on the IdentityServer side's client config or it'll be null again, and you would have missed it among all the other parameters. For instance, these methods of setting PostLogoutRedirectUri DO NOT work:
n.ProtocolMessage.SetParameter("PostLogoutRedirectURI", "some URL");
n.ProtocolMessage.SetParameter("PostLogoutUri", "another URL");
n.ProtocolMessage.SetParameter("PostLogoutRedirectUri", "yet another URL that's going to be ignored by IdentityServer4");
From there, you're off to the races because PostLogoutRedirectUri is no longer null. There are a few more considerations: check out AccountOptions in the IdentityServer controller folder. I set AutomaticRedirectAfterSignout to true there (this is used by Javascript in IdSrv's final logout page - if set, the script uses PostLogoutRedirectUri to forward the user back to the client). There's also an option to show a logout confirmation prompt, which if you want to actually display, make sure to NOT set the id token hint in the Owin (it's right next to where we set the PostLogoutRedirectUri / the part that gets triggered by signout requests). If you do those two things, AccountServices.BuildLogoutViewModel will return the prompt to the user when it's called by the AccountController.logout() method.
Thank you aaronR for the answer to my other question concerning that part:
IdentityServer4 logout (id token hint tells IdentityServer that the signout request was authorized and not a malicious person trying to harass your system / sign out users, IdSrv will ask the user for confirmation if it's not provided).
Finally, if you are confused by what's happening on the IdentityServer side in logout, and why it's repeatedly triggering the same method:
First time it gets called from the client's Owin middleware (the bit of code above that gets triggered after the Signout() action).
It uses AccountService to build a view model to return to the user for logout confirmation.
It gets triggered again by the user clicking yes on that page.
It goes through the Account service method again, which this time sets the bool to show the logout confirmation to false.
It calls the second logout method, the one with the view model that gets passed in.
This one triggers the external identity provider signout.
The external identity provider returns control back to logout, resulting in it getting called again, calling the second logout method again.
Finally, it will return the user to IdentityServer's logout page. If PostLogoutRedirectUri is set & AutomaticRedirectAfterSignOut is true, there's javascript on that page which automatically forwards the user's browser to it.
Due to having two projects to debug through at once and all of these possible ways of setting the redirect (which also have to match client/server side config in order not to be null) it can be easy to get confused.
Overview
When setting up IdentityServer (assuming it's a separate application), there are two parameters in the configuration for an accessing client: RedirectUris and PostLogoutRedirectUris. These correspond to what happens after a login or logout of a user against the IdentityServer system.
Since your client app probably has its own cookies (or JWT tokens, or whatever it's using to maintain a user session), it needs to know when the IdentityServer has processed the login and made the user data available.
The default ASP.NET OpenID Connect middleware does this with yourapp.com/signin-oidc and yourapp.com/signout-callback-oidc endpoints to intercept and handle the login/logout hand-off from IdentityServer. These endpoints have nothing to do with the OpenID protocol and can be set to whatever you want if you have your own authentication handler, but if you're using the default middleware then you must set them to that in the IdentityServer config.
Config option
However, if you still want to redirect a user after the OpenID Connect logout has completed, there's an option specifically for this:
services.AddOpenIdConnect(options =>
{
// your other options...
options.SignedOutRedirectUri = "/some-page-after-oidc-logout";
});
Microsoft Docs
I want to share how I solved problem with null PostLogoutRedirectUri value.
I always had null PostLogoutRedirectUri value in logout context until I added SignInScheme value on mvc client side.
These settings of authentication on MVC client side works for me:
var authenticationBuilder = services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
});
authenticationBuilder.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.Cookie.Name = "identity_server_mvc";
});
authenticationBuilder.AddOpenIdConnect("oidc", options =>
{
options.Authority = "{IDENTITY_SERVER_URL}";
options.ClientId = "mvc";
options.SaveTokens = true;
options.SignInScheme = "Cookies";
});
You also need to make sure that you have added the PostLogoutRedirectUri value to the client configuration on the Identity Server side:
new Client
{
ClientId = "mvc",
AllowedGrantTypes = GrantTypes.Implicit,
RedirectUris = { "{CLIENT_URL}/signin-oidc" },
PostLogoutRedirectUris = { "{CLIENT_URL}/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
}
}
One last but important point, because of this I had null logoutId value on Identity Server side. To initiate Logout process you must first call SignOut("Cookies", "oidc") on mvc client side. Example endpoint in my HomeController:
public IActionResult Logout()
{
return SignOut("Cookies", "oidc");
}
Good luck!
Building on top of #McGuireV10 answer if your project is a Blazor WASM, then the change would be like this:
// Adds OpenID Connect Authentication
builder.Services.AddOidcAuthentication(options =>
{
options.ProviderOptions.Authority = settings.Authentication.Authority;
options.ProviderOptions.ClientId = settings.Authentication.ClientId;
options.ProviderOptions.ResponseType = "code";
options.ProviderOptions.ResponseMode = "query";
//
options.AuthenticationPaths.LogOutCallbackPath = "authentication/logout-callback";
builder.Configuration.Bind("oidc", options.ProviderOptions);
});
I ran into the same issue today; your (#JakeJ) link solved it for me. I am building a demo MVC Owin Client in .net 4.6.1 (for a third party company) so our set up is the same and our Id Svr v4 is built on net core v3.1.
I verified i had the same PostLogoutRedirectUri defined in the Id Svr side config for the client i was working on and then at the client side config too.
But i noticed that i could add a small block of code taken from the ref'ed github issue to the RedirectToIdentityProvider func delegate specific to logout.
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
// below is technically not needed, as it was already set for me.
n.ProtocolMessage.PostLogoutRedirectUri = LoginAndOutRedirectUri;
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token").Value;
n.ProtocolMessage.IdTokenHint = idTokenHint;
}
This means that a claim needs to be present in order for this to work so i then added the below to the SecurityTokenValidated func delegate:
// add id token for logout
currentIdentity.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
I saw many examples where folks were populating the AuthenticationTicket inside the AuthorizationCodeReceived func delegate but for me it was always null. So some head scratching lead me to implementing what i needed inside the SecurityTokenValidated delegate. And it all works and hands together nicely.
The API is a backend to a mobile app. I don't need user authentication. I simply need a way to secure access to this API. Currently, my backend is exposed.
The documentation seems to only talk about user authentication and authorization, which is not what I need here. I just need to ensure only my mobile app can talk to this backend and no one else.
Yes, you can do that: use authentication to secure your endpoints without doing user authentication.
I have found that this way of doing it is not well documented, and I haven't actually done it myself, but I intend to so I paid attention when I saw it being discussed on some of the IO13 videos (I think that's where I saw it):
Here's my understanding of what's involved:
Create a Google API project (though this doesn't really involve their API's, other than authentication itself).
Create OATH client ID's that are tied to your app via its package name and the SHA1 fingerprint of the certificate that you will sign the app with.
You will add these client ID's to the list of acceptable ID's for your endpoints. You will add the User parameter to your endpoints, but it will be null since no user is specified.
#ApiMethod(
name = "sendInfo",
clientIds = { Config.WEB_CLIENT_ID, Config.MY_APP_CLIENT_ID, Config.MY_DEBUG_CLIENT_ID },
audiences = { Config.WEB_CLIENT_ID }
// Yes, you specify a 'web' ID even if this isn't a Web client.
)
public void sendInfo(User user, Info greeting) {
There is some decent documentation about the above, here:
https://developers.google.com/appengine/docs/java/endpoints/auth
Your client app will specify these client ID's when formulating the endpoint service call. All the OATH details will get taken care of behind the scenes on your client device such that your client ID's are translated into authentication tokens.
HttpTransport transport = AndroidHttp.newCompatibleTransport();
JsonFactory jsonFactory = new JacksonFactory();
GoogleAccountCredential credential = GoogleAccountCredential.usingAudience( ctx, Config.WEB_CLIENT_ID );
//credential.setSelectedAccountName( user ); // not specify a user
Myendpoint.Builder builder = new Myendpoint.Builder( transport, jsonFactory, credential );
This client code is just my best guess - sorry. If anyone else has a reference for exactly what the client code should look like then I too would be interested.
I'm sorry to say that Google doesn't provide a solution for your problem (which is my problem too).
You can use their API key mechanism (see https://developers.google.com/console/help/new/#usingkeys), but there is a huge hole in this strategy courtesy of Google's own API explorer (see https://developers.google.com/apis-explorer/#p/), which is a great development tool to test API's, but exposes all Cloud Endpoint API's, not just Google's services API's. This means anyone with the name of your project can browse and call your API at their leisure since the API explorer circumvents the API key security.
I found a workaround (based on bossylobster's great response to this post: Simple Access API (Developer Key) with Google Cloud Endpoint (Python) ), which is to pass a request field that is not part of the message request definition in your client API, and then read it in your API server. If you don't find the undocumented field, you raise an unauthorized exception. This will plug the hole created by the API explorer.
In iOS (which I'm using for my app), you add a property to each request class (the ones created by Google's API generator tool) like so:
#property (copy) NSString *hiddenProperty;
and set its value to a key that you choose. In your server code (python in my case) you check for its existence and barf if you don't see it or its not set to the value that your server and client will agree on:
mykey,keytype = request.get_unrecognized_field_info('hiddenProperty')
if mykey != 'my_supersecret_key':
raise endpoints.UnauthorizedException('No, you dont!')
Hope this puts you on the right track
The documentation is only for the client. What I need is documentation
on how to provide Service Account functionality on the server side.
This could mean a couple of different things, but I'd like to address what I think the question is asking. If you only want your service account to access your service, then you can just add the service account's clientId to your #Api/#ApiMethod annotations, build a GoogleCredential, and invoke your service as you normally would. Specifically...
In the google developer's console, create a new service account. This will create a .p12 file which is automatically downloaded. This is used by the client in the documentation you linked to. If you can't keep the .p12 secure, then this isn't much more secure than a password. I'm guessing that's why this isn't explicitly laid out in the Cloud Endpoints documentation.
You add the CLIENT ID displayed in the google developer's console to the clientIds in your #Api or #ApiMethod annotation
import com.google.appengine.api.users.User
#ApiMethod(name = "doIt", scopes = { Constants.EMAIL_SCOPE },
clientIds = { "12345678901-12acg1ez8lf51spfl06lznd1dsasdfj.apps.googleusercontent.com" })
public void doIt(User user){ //by convention, add User parameter to existing params
// if no client id is passed or the oauth2 token doesn't
// match your clientId then user will be null and the dev server
// will print a warning message like this:
// WARNING: getCurrentUser: clientId 1234654321.apps.googleusercontent.com not allowed
//..
}
You build a client the same way you would with the unsecured version, the only difference being you create a GoogleCredential object to pass to your service's MyService.Builder.
HttpTransport httpTransport = new NetHttpTransport(); // or build AndroidHttpClient on Android however you wish
JsonFactory jsonFactory = new JacksonFactory();
// assuming you put the .p12 for your service acccount
// (from the developer's console) on the classpath;
// when you deploy you'll have to figure out where you are really
// going to put this and load it in the appropriate manner
URL url = getClass().class.getResource("/YOURAPP-b12345677654.p12");
File p12file = new File(url.toURI());
GoogleCredential.Builder credentialBuilder = new GoogleCredential.Builder();
credentialBuilder.setTransport(httpTransport);
credentialBuilder.setJsonFactory(jsonFactory);
//NOTE: use service account EMAIL (not client id)
credentialBuilder.setServiceAccountId("12345678901-12acg1ez8lf51spfl06lznd1dsasdfj#developer.gserviceaccount.com"); credentialBuilder.setServiceAccountScopes(Collections.singleton("https://www.googleapis.com/auth/userinfo.email"));
credentialBuilder.setServiceAccountPrivateKeyFromP12File(p12file);
GoogleCredential credential = credentialBuilder.build();
Now invoke your generated client the same way
you would the unsecured version, except the builder takes
our google credential from above as the last argument
MyService.Builder builder = new MyService.Builder(httpTransport, jsonFactory, credential);
builder.setApplicationName("APP NAME");
builder.setRootUrl("http://localhost:8080/_ah/api");
final MyService service = builder.build();
// invoke service same as unsecured version
I am currently working on a small project using RESTlet on Google App Engine/Java.
I was searching.. searching.. and couldn't find the exact or understandable solutions for my doubts.
My question is that How am I suppose to implement my own SignIn & SignUp module without using google's UserService or Spring Security??
Is there any actual sample code available??
I mean SignUp part is just a simple JDO insert & select module. let's just say I've done it.
How am I supposed to handle each user's request session and authentication??
I am thinking about using HTTPS on every request.
Any suggestions or help would be really appreciated!
Thanks in advance.
In Restlet, you have security support on both client and server sides. On client side, you can specify security hints using the ChallengeResponse entity. This feature is open and you can specify the authentication type you want. In the following code, I use an http basic authentication based on username / password:
ClientResource cr = new ClientResource(uri);
ChallengeScheme scheme = ChallengeScheme.HTTP_BASIC;
ChallengeResponse authentication = new ChallengeResponse(
scheme, "username", "password");
cr.setChallengeResponse(authentication);
Restlet will automatically build necessary headers in the corresponding request. You can note that Restlet supports a wide range of authentication types through its extensions. I know that some work is done at the moment to support OAuth v2 (see http://wiki.restlet.org/developers/172-restlet/257-restlet/310-restlet.html).
On the server side, you need to secure accesses at routing level using the ChallengeAuthenticator entity, as described below. This can be done within your Restlet application:
public Restlet createInboundRoot() {
Router router = new Router(getContext());
ChallengeAuthenticator guard = new ChallengeAuthenticator(getContext(),
ChallengeScheme.HTTP_BASIC, "realm");
guard.setVerifier(verifier);
guard.setEnroler(enroler);
guard.setNext(router);
return guard;
}
Like for client side, this support is generic and is based on two interfaces that need to be specified on the guard:
The verifier one to check if authentication is successful
The enroler one to fill roles for the authenticated user
You can notice that same security technologies need to be use on both sides...
If you want to manage authentication session for user, you need to implement it by yourself using cookies.
When authentication successes on server side, you can return a cookie containing a security token that allows you checking the user from your database (for example). Some code like below can implement that:
CookieSetting cookie = new CookieSetting(0,
SECURITY_COOKIE_NAME, securityToken);
Series<CookieSetting> cookieSettings = response.getCookieSettings();
cookieSettings.clear();
cookieSettings.add(cookie);
You can extend for example the SecretVerifier class of Restlet to add a test on security data received and add this code when receiving the security cookie.
On client side, you need to add hints for authentication the first time and then re send the security cookie following times, as described below:
ClientResource clientResource = (...)
(...)
Cookie securityCookie = new Cookie(0,
SECURITY_COOKIE_NAME, securityToken);
clientResource.getRequest().getCookies().clear();
clientResource.getRequest().getCookies().add(securityCookie);
Hope it will help you!
Thierry
If you are interested in re-using social accounts, you need to integrate with each one like facebook oauth
And/Or use the app engine authentication via OpenID
Both ways define an API to authenticate a client, you can use the UserService or manage your own state via cookies.