IdentityServer3.AccessTokenValidation API and IdentityServer4 - asp.net-web-api

I get the access token from the IdSrv4 and when i try to call my api with that token
var client = new HttpClient();
client.SetBearerToken(token.AccessToken);
var response = await client.GetAsync("http://localhost:60602/api/users");
i get this error message:
Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationMiddleware
Error: 0 : Authentication failed System.InvalidOperationException:
Sequence contains no elements at
System.Linq.Enumerable.First[TSource](IEnumerable1 source) at
IdentityServer3.AccessTokenValidation.DiscoveryDocumentIssuerSecurityTokenProvider.<RetrieveMetadata>b__1(JsonWebKey
key) in
c:\local\identity\server3\AccessTokenValidation\source\AccessTokenValidation\Plumbing\DiscoveryDocumentIssuerSecurityTokenProvider.cs:line
152 at System.Linq.Enumerable.WhereSelectListIterator2.MoveNext()
at
System.IdentityModel.Tokens.JwtSecurityTokenHandler.ResolveIssuerSigningKey(String
token, SecurityToken securityToken, SecurityKeyIdentifier
keyIdentifier, TokenValidationParameters validationParameters) at
System.IdentityModel.Tokens.JwtSecurityTokenHandler.ValidateSignature(String
token, TokenValidationParameters validationParameters) at
System.IdentityModel.Tokens.JwtSecurityTokenHandler.ValidateToken(String
securityToken, TokenValidationParameters validationParameters,
SecurityToken& validatedToken) at
Microsoft.Owin.Security.Jwt.JwtFormat.Unprotect(String protectedText)
at
Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationHandler.d__0.MoveNext()
I read this issue and add certificate generated by this code
https://github.com/ElemarJR/LearningIdentityServer4/tree/master/LearningIdentityServer.OAuth
but without success.
WebApi code
...
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
JwtSecurityTokenHandler.InboundClaimTypeMap.Clear();
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://localhost:5000",//Constants.BaseAddress,
RequiredScopes = new[] { "api1" },
});
...
any suggestions ?

I had same problem; Read following links
[enter link description here][1]
Identity server 4 token not validate in .NetFramework Api that use Identity Server 3
and
https://github.com/IdentityServer/IdentityServer3.AccessTokenValidation/issues/124
in teh summery you must upgrade identityserver3.accesstokenvalidation to " v2.13.0"

Related

Identity server 4 throws redirect uri not define error

I am trying connect the asp.net webform client to identity server 4 for authentication and athorization. When user is redirected to identity server for login I am getting an error and Identity server log says "redirect_uri is missing or too long" but I have defined the redirect uri on client config. Not sure why it is throwing an error on identity server side?
Client Configuration:
new Client {
ClientId = "testclient1",
ClientSecrets = { new Secret("client_secret_webform".ToSha256()) },
AllowedGrantTypes = GrantTypes.Implicit,
RequirePkce = true,
RedirectUris = { "http://localhost:54602/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:54602/signin-oidc" },
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
}
Webform Client setting
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "testclient1",
Authority = "https://localhost:44314/",
ClientSecret = "client_secret_webform",
ResponseType = "id_token token",
SaveTokens = true
});
}
IdentityServer4.Validation.AuthorizeRequestValidator: Error: redirect_uri is missing or too long
The easiest way to figure out the correct redirectUrl is to locate the request to the Authenticate endpoint in Fiddler, and see what RedirectUrl the OpenIDConnect handler is sending to IdentityServer.
In Fiddler you can then locate the redirect_uri that should match exactly the url defined in IdentityServer
Try to change OpenID Connect settings on client to add RedirectUri and PostLogoutRedirectUri, like this:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "testclient1",
Authority = "https://localhost:44314/",
ClientSecret = "client_secret_webform",
ResponseType = "id_token token",
SaveTokens = true,
RedirectUri = "http://localhost:54602/signin-oidc",
PostLogoutRedirectUri = "http://localhost:54602/signin-oidc",
});
}
Also as other answer mentioned use Fiddler or activate log to check the values sent to IDS4
The redirect uri you configure in IdentityServer is not the same as the one you are actually using in the client request. Capture your request to see this redirect uri.
The exception can be caused by two resons:
The redirect Uri is empty, null or white space
The redirect Uri is longer than the maximum allowed. The default value is 400.
You can modify this value in the IdentityServerOptions:
services
.AddIdentityServer(options =>
{
options.InputLengthRestrictions.RedirectUri = 1000;
...
}
...
...

Google Sign-In for server-side apps using .net

I have managed to establish a login screen using Javascript google API as described in https://developers.google.com/identity/sign-in/web/server-side-flow. using the function: auth2.grantOfflineAccess()
The API returns the following authorization code:
{"code":"4/yU4cQZTMnnMtetyFcIWNItG32eKxxxgXXX-Z4yyJJJo.4qHskT-UtugceFc0ZRONyF4z7U4UmAI"}
How do I exchange the authorization code to access token and a refresh token in an ASP.NET server?
Basically you need to post it back to google to get an access token and a refresh token.
POST https://accounts.google.com/o/oauth2/token
code={CODE}&client_id={ClientId}&client_secret={ClientSecret}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=authorization_code
Here is a server-side code for for google sign in:
UserCredential credential;
string[] scopes = new string[] {
YouTubeService.Scope.YoutubeUpload
};
IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "<CLIENT-ID>",
ClientSecret = "<CLIENT-SECRET>"
},
Scopes = scopes,
DataStore = new FileDataStore("Store")
});
TokenResponse token = flow.ExchangeCodeForTokenAsync(videoUploadOptions.userId, videoUploadOptions.authorizationCode, videoUploadOptions.baseUrl, CancellationToken.None).Result;
credential = new UserCredential(flow, Environment.UserName, token);
The auth2 google response will be stored at the token object. the variable transferred from the client are: videoUploadOptions.userId, videoUploadOptions.authorizationCode and videoUploadOptions.baseUrl.
All the credentials will be stored at the credential object.

AAD Token from SPA app works to call Nodejs API but not .Net WebAPI

I have a SPA application that uses MSAL to acquire a token from AAD. Because MSAL works with v2 endpoint and because v2 endpoint does not currently support issuing tokens for custom API's, I'm passing the ID Token to my api and essentially treating my api as the same application. (While this has a smell to it, it does actually work -- at least with Nodejs API).
SPA app
let idToken = Msal.Storage('localStorage').getItem(Msal.Constants.idTokenKey);
this.http.configure(config => {
config.withBaseUrl("http://localhost:3001/")
config.withDefaults({headers: {'Authorization': 'Bearer ' + idToken}})
});
//Call API
this.http.fetch("account")
...
Node.js API
//Using express/passport
var BearerStrategy = require("passport-azure-ad").BearerStrategy;
var options = {
identityMetadata: "https://login.microsoftonline.com/tenantid/.well-known/openid-configuration/",
clientID: "xxxxxxx-xxxx-xxxxxxx-xxxxx",
passReqtoCallback: false,
validateIssuer: true,
issuer: "http://login.microsoftonline.com/{tenantid}/v2.0"
};
app.get("/account",passport.authenticate('oauth-bearer',{session: false}),...
The above all works. Once a user authenticates with the SPA, the token is passed and the call to the Node API works.
I'm now trying to replace the Nodejs API with a .Net WebAPI. I have the following:
Startup.cs
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters
{
//Same ID as used for ClientID in Nodejs
ValidAudience = "xxxxxx-xxxxx-xxxxx-xxxxx",
ValidIssuer = "https://login.microsoftonline.com/{tenantid}/v2.0",
ValidateIssuer = true,
AuthenticationType = "WebApi" //Tried both with and without this
},
Tenant = "{tenantid}" //have tried both id and name
}
)
AccountController.cs
[Authorize]
[Route("account")]
public IHttpActionResult AccountProfile(){
//Get Account information
....
return Ok(profile);
}
However, when I point the SPA app to call the .Net api, I always get Authorization has been denied for this request .
Is there something I'm missing?
Edit
Incidentally, I've inspected the token that is being used.
The value I'm using for clientID (Nodejs) and ValidAudience (.Net) exactly match the aud claim in the token. The issuer (Nodejs) and ValidIssuer (.Net) exactly match the iss claim in the token. Lastly, the anywhere in my code where I've inserted {tenantid}, the actual value there matches exactly the tid claim in the token.
We had a similar issue when switching from ADAL to MSAL and got it to work by using a similar approach like this Github project. Specifically take a look at these files:
https://github.com/oktadeveloper/okta-oauth-aspnet-codeflow/blob/master/Api/Startup.cs
https://github.com/oktadeveloper/okta-oauth-aspnet-codeflow/blob/master/Api/OpenIdConnectCachingSecurityTokenProvider.cs
Update: Our Startup.cs:
var provider = new OpenIdConnectCachingSecurityTokenProvider(
string.Format(bc2Instace, tenant, policyId));
var jwt = new JwtFormat(clientId, provider);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = jwt,
});

How to know that access token has expired?

How should client know that access token has expired, so that he makes a request with refresh token for another access token?
If answer is that server API will return 401, then how can API know that access token has expired?
I'm using IdentityServer4.
Your api should reject any call if the containing bearer token has already been expired. For a webapi app, IdentityServerAuthenticationOptions will do the work.
But your caller Web application is responsible for keeping your access_token alive. For example, if your web application is an ASP.Net core application, you may use AspNetCore.Authentication.Cookies to authenticate any request. In that case, you can find the information about the token expiring info through OnValidatePrincipal event.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
//ExpireTimeSpan = TimeSpan.FromSeconds(100),
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async x =>
{
if (x.Properties?.Items[".Token.expires_at"] == null) return;
var now = DateTimeOffset.UtcNow;
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
var timeElapsed = now.Subtract(x.Properties.IssuedUtc.Value);
var timeRemaining = tokenExpireTime.Subtract(now.DateTime);
if (timeElapsed > timeRemaining)
{
//Get the new token Refresh the token
}
}
}
}
I have added a full implementation about how to get a new access token using refresh token in another StackOverflow answer

google ExchangeCodeForTokenAsync invalid_grant in webapi

i have implemented GoogleAuthorizationCodeFlow scenario from google api client dotnet and tutorial to get token from what my client sent to server as a code. but when i call flow.ExchangeCodeForTokenAsync , I get the following error :
{"Error:\"invalid_grant\", Description:\"\", Uri:\"\""}
I read google authorization invalid_grant and gusclass oauth 2 using google dotnet api client libraries but they didn't help me and. I think it must be very simple but I don't know why it doesn't work.
For client side , I have used Satellizer and this is my server Codes:
public bool PostExchangeAccessToken(GoogleClientAccessCode code)
{
string[] SCOPES = { "email" };
IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets()
{
ClientSecret = "******",
ClientId = "********.apps.googleusercontent.com"
},
Scopes = SCOPES
});
try
{
TokenResponse token;
token = flow.ExchangeCodeForTokenAsync("*****#gmail.com", Newtonsoft.Json.JsonConvert.SerializeObject(code), "https://localhost:44301/",
CancellationToken.None).Result;
}
catch (Exception ex)
{
throw ex;
}
return true;
}
what is the problem?
On Github I found that I must use the Token from the client and use
GoogleAuthorizationCodeFlow.Initializer()
to create my UserCredential object.
You can check your google developer console settings.(Authorized redirect URIs)
Credentials => OAuth 2.0 client IDs => Your Application Settings => Authorized redirect URIs
You must add url. ("https://localhost:44301/")
My code :
flow.ExchangeCodeForTokenAsync("me", authCode, redirectUri, CancellationToken.None).Result;
Authorized redirect URIs
For use with requests from a web server. This is the path in your application that users are redirected to after they have authenticated with Google. The path will be appended with the authorization code for access. Must have a protocol. Cannot contain URL fragments or relative paths. Cannot be a public IP address.

Resources