Identity server 4 throws redirect uri not define error - webforms

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;
...
}
...
...

Related

Calling rest server from mobile app

Following on from https://lists.hyperledger.org/g/composer/message/91
I have adapted the methodology described by Caroline Church in my IOS app.
Again I can authenticate with google but still get a 401 authorization error when POSTing.
I have added the withCredentials parameter to the http header in my POST request.
does the rest server pass back the token in cookie ? I don't receive anything back from the rest server.
where does the withCredentials get the credentials from ?
COMPOSER_PROVIDERS as follows
COMPOSER_PROVIDERS='{
"google": {
"provider": "google",
"module": "passport-google-oauth2",
"clientID": "93505970627.apps.googleusercontent.com",
"clientSecret": "",
"authPath": "/auth/google",
"callbackURL": "/auth/google/callback",
"scope": "https://www.googleapis.com/auth/plus.login",
"successRedirect": "myAuth://",
"failureRedirect": "/"
}
}'
the successRedirect points back to my App. After successfully authenticating I return to the App.
Got this working now. The App first authenticates with google then exchanges the authorization code with the rest server.
The Rest server COMPOSER_PROVIDERS needs to be changed to relate back to the app.
clientID is the apps ID in google,
callbackURL and successRedirect are reversed_clientID://
The App calls http://localhost:3000/auth/google/callback with the authorization code as a parameter.
this call will fail, but an access_token cookie is written back containing the access token required for the rest server.
The user id of the logged in user is not passed back, when exchanging the code for a token with google we get back a JWT with the details of the logged in user. We need this back from the rest server as well as the token. Is there any way to get this ?
changing the COMPOSER_PROVIDERS means that the explorer interface to the Rest server no longer works.
func getRestToken(code: String) {
let tokenURL = "http://localhost:3000/auth/google/callback?code=" + code
let url = URL(string:tokenURL);
var request = URLRequest(url: url!);
request.httpMethod = "GET";
request.setValue("localhost:3000", forHTTPHeaderField: "Host");
request.setValue("text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8", forHTTPHeaderField: "Accept");
request.setValue("1", forHTTPHeaderField: "Upgrade-Insecure-Requests");
request.httpShouldHandleCookies = true;
request.httpShouldUsePipelining = true;
let session = URLSession.init(configuration: .default);
session.configuration.httpCookieAcceptPolicy = .always;
session.configuration.httpShouldSetCookies=true;
session.configuration.httpCookieStorage = HTTPCookieStorage.shared;
let task = session.dataTask(with: request) { (data, response, error) in
var authCookie: HTTPCookie? = nil;
let sharedCookieStorage = HTTPCookieStorage.shared.cookies;
// test for access_token
for cookie in sharedCookieStorage! {
if cookie.name == "access_token"
{
print(“Received access token”)
}
}
guard error == nil else {
print("HTTP request failed \(error?.localizedDescription ?? "ERROR")")
return
}
guard let response = response as? HTTPURLResponse else {
print("Non-HTTP response")
return
}
guard let data = data else {
print("HTTP response data is empty")
return
}
if response.statusCode != 200 {
// server replied with an error
let responseText: String? = String(data: data, encoding: String.Encoding.utf8)
if response.statusCode == 401 {
// "401 Unauthorized" generally indicates there is an issue with the authorization
print("Error 401");
} else {
print("HTTP: \(response.statusCode), Response: \(responseText ?? "RESPONSE_TEXT")")
}
return
}
}
task.resume()
}
have you authorised the redirect URI in your Google OAUTH2 configuration ?
This determines where the API server redirects the user, after the user completes the authorization flow. The value must exactly match one of the redirect_uri values listed for your project in the API Console. Note that the http or https scheme, case, and trailing slash ('/') must all match.
This is an example of an Angular 5 successfully using it Angular 5, httpclient ignores set cookie in post in particular the answer at the bottom
Scope controls the set of resources and operations that an access token permits. During the access-token request, your application sends one or more values in the scope parameter.
see https://developers.google.com/identity/protocols/OAuth2
The withCredentials option is set, in order to create a cookie, to pass the authentication token, to the REST server.
Finally this resource may help you https://hackernoon.com/adding-oauth2-to-mobile-android-and-ios-clients-using-the-appauth-sdk-f8562f90ecff

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

IdentityServer3.AccessTokenValidation API and IdentityServer4

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"

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.

Separating Auth and Resource Servers with AspNet.Security.OpenIdConnect - the Audience?

The example on the AspNet.Security.OpenIdConnect.Server looks to me like both an auth and resource server. I would like to separate those. I have done so.
At the auth server's Startup.Config, I have the following settings:
app.UseOpenIdConnectServer(options => {
options.AllowInsecureHttp = true;
options.ApplicationCanDisplayErrors = true;
options.AuthenticationScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.Issuer = new System.Uri("http://localhost:61854"); // This auth server
options.Provider = new AuthorizationProvider();
options.TokenEndpointPath = new PathString("/token");
options.UseCertificate(new X509Certificate2(env.ApplicationBasePath + "\\mycertificate.pfx","mycertificate"));
});
I have an AuthorizationProvider written, but I don't think it's relevant to my current issue (but possibly relevant). At its GrantResourceOwnerCredentials override, I hard-code a claims principal so that it validates for every token request:
public override Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsNotification context)
{
var identity = new ClaimsIdentity(OpenIdConnectDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.Name, "me");
identity.AddClaim(ClaimTypes.Email, "me#gmail.com");
var claimsPrincipal = new ClaimsPrincipal(identity);
context.Validated(claimsPrincipal);
return Task.FromResult<object>(null);
}
At the resource server, I have the following in its Startup.config:
app.UseWhen(context => context.Request.Path.StartsWithSegments(new PathString("/api")), branch =>
{
branch.UseOAuthBearerAuthentication(options => {
options.Audience = "http://localhost:54408"; // This resource server, I believe.
options.Authority = "http://localhost:61854"; // The auth server
options.AutomaticAuthentication = true;
});
});
On Fiddler, I ask for a token, and I get one:
POST /token HTTP/1.1
Host: localhost:61854
Content-Type: application/x-www-form-urlencoded
username=admin&password=aaa000&grant_type=password
So now I use that access token to access a protected resource from the resource server:
GET /api/values HTTP/1.1
Host: localhost:54408
Content-Type: application/json;charset=utf-8
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI.....
I now get this error - Audience validation failed. Audiences: 'empty'. Did not match validationParameters.ValidAudience: 'http://localhost:54408' or validationParameters.ValidAudiences: 'null'.
I think the reason why is because I never set an audience at the auth server (at app.UseOpenIdConnectServer(...)), so I don't think it wrote audience info to the token. So I need to set an audience at the auth server (as what is done in IdentityServer3), but I can't find a property on the options object that would let me do that.
Does AspNet.Security.OpenIdConnect.Server require the auth and resource to be in the same server?
Is setting the audience done when putting together the ClaimsPrincipal, and if so, how?
Would I need to write a custom Audience validator and hook it up to the system? (I sure hope the answer to this is no.)
Does AspNet.Security.OpenIdConnect.Server require the auth and resource to be in the same server?
No, you can of course separate the two roles.
As you've already figured out, if you don't explicitly specify it, the authorization server has no way to determine the destination/audience of an access token, which is issued without the aud claim required by default by the OAuth2 bearer middleware.
Solving this issue is easy: just call ticket.SetResources(resources) when creating the authentication ticket and the authorization server will know exactly which value(s) (i.e resource servers/API) it should add in the aud claim(s).
app.UseOpenIdConnectServer(options =>
{
// Force the OpenID Connect server middleware to use JWT tokens
// instead of the default opaque/encrypted token format used by default.
options.AccessTokenHandler = new JwtSecurityTokenHandler();
});
public override Task HandleTokenRequest(HandleTokenRequestContext context)
{
if (context.Request.IsPasswordGrantType())
{
var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "unique identifier");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
// Call SetResources with the list of resource servers
// the access token should be issued for.
ticket.SetResources("resource_server_1");
// Call SetScopes with the list of scopes you want to grant.
ticket.SetScopes("profile", "offline_access");
context.Validate(ticket);
}
return Task.FromResult(0);
}
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Audience = "resource_server_1",
Authority = "http://localhost:61854"
});

Resources