I have set up an identity server using identityServer3. Now I am writing an iOS app to authenticate against the identity server. I could not find any example or codes for this setup. The examples I found are all using google, or github etc. My problem is that the call OAuthSwift.authorizeWithCallbackURL just switch the screen to safari and then nothing shows up. I suspect that my setup of callbackurl or url scheme is not correct.
Here is my code snippet
ON server side:
new Client {
ClientId = "implicitclient1",
ClientName = "Example Implicit Client1",
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
Enabled = true,
Flow = Flows.Implicit,
RequireConsent = true,
AllowRememberConsent = true,
RedirectUris =
new List<string> {"oauth-swift://oauth-callback/TestOAuth2"},
AllowedScopes = new List<string> {
Constants.StandardScopes.OpenId,
Constants.StandardScopes.Profile,
Constants.StandardScopes.Email
},
AccessTokenType = AccessTokenType.Jwt
},
on iOS app side:
let oauthswift = OAuth2Swift(
consumerKey: "implicitclient1",
consumerSecret: "secret",
authorizeUrl: "https://example.com/idserver/core/connect/authorize",
accessTokenUrl: "https://example.com/idserver/core/connect/token",
responseType: "token"
)
oauthswift.authorizeWithCallbackURL(NSURL(string: "oauth-swift://oauth-callback/TestOAuth2")!,
scope: "openid",state: "",
success: {
credential, response, parameters in
print(credential.oauth_token)},
failure: {
error in
print(error.localizedDescription)
})
When adding URL types to the ios app, I put in "TestOAuth2".
What did I do wrong?
Thanks a lot.
I got it working.
Turning on the identityserver3 logging was a tremendous help. I changed the flow from implicit to Authorization Code. ( I am not sure why this was needed but that was how I made it work.) I changed the call back string to "myiosappname://oauth-callback". On the ios app, I put "myiosappname" to plist's URL Types scheme.
I double checked that ssl certificate.
Then it started to work. I hope it helps someone down the road.
HZ
Related
I have an Angular 9 web application connected via the oidc-client to Identity Server 4 and an API using Implicit flow. When I get the authenticated user I can see several claims I want for the site such as the email address, the user name or the role.
I'm now trying to do the exact same thing using the password flow and I'm getting only the sub claim back - note this is the first time I use it and therefore it may not be right, but in essence, below would be the call I'm performing (using this time angular-oauth2-oidc through my ionic app) - for simplicity and for testing purposes I'm using postman to illustrate this:
I have modified my client to allow the profile scope without any luck and also I'm getting a different type of response and claim processing targetting the same user using the same configuration on IS4:
My question is, is there anything special I need to set up in my client when I use the password flow to get the claims back or do I need to modify the profile service to include them all the time? I would have imagined when you have access to different scopes and they have issued claims you should get them back but I'm not sure if I'm missing something fundamental here.
My client's config:
public static IEnumerable<Client> Get()
{
return new List<Client>
{
new Client
{
ClientId = "web",
ClientName = "Web Client",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RequireClientSecret = false,
AllowedScopes = new List<string> { "openid", "profile", "myapi" },
RedirectUris = new List<string> {
"http://<base-url>/auth-callback",
"http://<base-url>/silent-renew-callback",
},
PostLogoutRedirectUris = new List<string> {"http://<base-url>"},
AllowedCorsOrigins = new List<string> {"http://<base-url>"},
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
AlwaysSendClientClaims = true,
AlwaysIncludeUserClaimsInIdToken = true,
},
new Client
{
ClientId = "mobile",
ClientName = "Mobile Client",
ClientSecrets = { new Secret("t8Xa)_kM6apyz55#SUv[[Cp".Sha256()) },
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
AllowedScopes = new List<string> { "openid", "mobileapp", "myapi" },
AccessTokenType = AccessTokenType.Jwt,
AccessTokenLifetime = 3600,
IdentityTokenLifetime = 3600,
UpdateAccessTokenClaimsOnRefresh = false,
SlidingRefreshTokenLifetime = 30,
AllowOfflineAccess = true,
RefreshTokenExpiration = TokenExpiration.Absolute,
RefreshTokenUsage = TokenUsage.OneTimeOnly,
AlwaysSendClientClaims = true,
Enabled = true
}
};
}
}
Any tips are highly appreciated. Many thanks!
UPDATE: Since ROPC flow is being deprecated in oauth 2.1 (https://fusionauth.io/blog/2020/04/15/whats-new-in-oauth-2-1) I decided to move everything to the code flow + PKCE mechanism.
Password grant is an OAuth grant and is to obtain an access token. And what you see as a result of password grant is an access token. access token does not contain any information about the user itself besides their ID (sub claim).
But Implicit grant you use is OpenId Grant. You use oidc client lib and use "openid", "profile" on client - AllowedScopes. What you get in result in an id token. This token authenticates the user to the application and contains user info.
Read more about tokens here.
And this is a very good post which Diagrams of All The OpenID Connect Flows
I am trying to implement OAuth to one of my companies' projects and can't resolve the following problem.
We used IdentityServer4 for implementing our own Authorization Server, which works fine so far. The resource I want to protect with OAuth is a WebApi utilizing Swagger/Swashbuckle.
I followed the IdentityServer4 QuickStartExamples to configure the server and this tutorial [Secure Web APIs with Swagger, Swashbuckle, and OAuth2 (part 2)](http://knowyourtoolset.com/2015/08/secure-web-apis-with-swagger-swashbuckle-and-oauth2-part-2 for configuring Swagger/Swashbuckle).
I have a dummy-action which does nothing else than returning a string, that works as expected.
When I decorate the action with [Authorize], a little red icon appears in swagger-ui, indicating that I have to log in to access this method. The Login process works fine: I am redirected to the Quickstart-UI, can login with the testuser "Bob", and I am redirected to swagger-ui after a successful login.
The problem: After the successful login, I still get an 401 error, stating "Authorization has been denied for this request."
I can see that a bearer token is returned by my IdentityServer in swagger-ui, so I guess this part working fine and the problem seems to be swagger/swashbuckle.
Is there maybe anything else I have to do with the token? In the tutorials I read so far, the swagger config is modified as I did it (see below) and that's it, so I guess swagger/swashbuckle should handle this - but maybe I miss out something?
SwaggerConfig.cs:
c.OAuth2("oauth2")
.Description("OAuth2 Implicit Grant")
.Flow("implicit") //also available: password, application (=client credentials?)
.AuthorizationUrl("http://localhost:5000/connect/authorize")
.TokenUrl("http://localhost:5000/connect/token")
.Scopes(scopes =>
{
scopes.Add("My.Web.Api", "THE Api");
});
// etc. .....
c.OperationFilter<AssignOAuth2SecurityRequirements>();
// etc. .....
c.EnableOAuth2Support(
clientId: "swaggerui",
clientSecret: "secret",
realm: "dummyrealm",
appName: "Swagger UI"
);
Filter for Authorize Attribute in SwaggerConfig.cs:
public class AssignOAuth2SecurityRequirements : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
// Determine if the operation has the Authorize attribute
var authorizeAttributes = apiDescription
.ActionDescriptor.GetCustomAttributes<AuthorizeAttribute>();
if (!authorizeAttributes.Any())
return;
// Initialize the operation.security property
if (operation.security == null)
operation.security = new List<IDictionary<string, IEnumerable<string>>>();
// Add the appropriate security definition to the operation
var oAuthRequirements = new Dictionary<string, IEnumerable<string>>
{
{ "oauth2", new [] { "My.Web.Api" } }
};
operation.security.Add(oAuthRequirements);
}
}
IdentityServer api config:
new ApiResource("My.Web.Api", "THE Api")
IdentityServer client config:
new Client
{
ClientId = "swaggerui",
ClientName = "Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
AllowedCorsOrigins = { "http://localhost:5858" },
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:5858/swagger/ui/o2c-html" },
PostLogoutRedirectUris = { "http://localhost:5858/swagger/ui/o2c-html" },
AllowedScopes =
{
"My.Web.Api"
}
Screenshot of redirection after login:
When using .NET Core (but it would appear that this question is for .NET Framework) I also encountered this same problem. It was solved by ensuring that in the Configure method of Startup you have UseAuthentication before UseAuthorization
(source https://learn.microsoft.com/en-us/aspnet/core/grpc/authn-and-authz?view=aspnetcore-3.1)
Following https://developers.google.com/identity/sign-in/web/server-side-flow After getting the authorization code from JavaScript, and passing it to the server side, we indeed get an access token (and an ID token), but not the required refresh token.
There are many posts around but could not solve it yet.
Any suggestion how to get the refresh token?
thanks!
private String getResponseToken(GoogleClientSecrets clientSecrets,
String authCode) throws IOException {
try {
GoogleTokenResponse tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
"https://www.googleapis.com/oauth2/v4/token",
// "https://accounts.google.com/o/oauth2/token",
clientSecrets.getDetails().getClientId(),
clientSecrets.getDetails().getClientSecret(),
authCode, //NOTE: was received from JavaScript client
"postmessage" //TODO: what's this?
).execute();
String accessToken = tokenResponse.getAccessToken();
String idToken = tokenResponse.getIdToken();
//TODO: not getting a refresh token... why?!
String refreshToken = tokenResponse.getRefreshToken();
Boolean hasRefreshToken = new Boolean(!(refreshToken == null));
LOGGER.warn("received refresh token: {}", hasRefreshToken);
LOGGER.debug("accessToken: {}, refreshToken: {}, idToken: {}", accessToken, refreshToken, idToken);
return accessToken;
}catch (TokenResponseException tre){...}
Gmail API only gives the refresh token the first time you ask for the users permission. (At least this is what happens to me).
Go to: https://myaccount.google.com/permissions?pli=1, remove the authorization to your app and run your code. You should receive the refresh token.
you should add the
AccessType = "offline"
You need to call the function
new GoogleAuthorizationCodeRequestUrl(...).setAccessType("offline")
or another syntax:
var authReq = new GoogleAuthorizationCodeRequestUrl(new Uri(GoogleAuthConsts.AuthorizationUrl)) {
RedirectUri = Callback,
ClientId = ClientId,
AccessType = "offline",
Scope = string.Join(" ", new[] { Scopes... }),
ApprovalPrompt = "force"
};
in Fiddler you should see the following request:
https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/webmasters&redirect_uri=http://mywebsite.com/google/scapi/callback/&response_type=code&client_id=xxx&access_type=offline
see also here
More details about setAccessType can be found here
after finding how to use the Google APIs at the backend (documentation is somewhat partial..), the issue was fixed at the FrontEnd side by tweaking a parameter:
grantOfflineAccess({
- prompt: 'select_account'
+ prompt: 'consent'
HTH
I am trying to access token URL working with IdentityServer3. The Server is configured the following way:
var options = new IdentityServerOptions
{
LoggingOptions = new LoggingOptions
{
WebApiDiagnosticsIsVerbose = true,
EnableWebApiDiagnostics = true,
EnableHttpLogging = true,
EnableKatanaLogging= true
},
Factory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get())
.UseInMemoryUsers(Users.Get()),
RequireSsl = false,
EnableWelcomePage = false,
};
app.UseIdentityServer(options);
The client configuration:
new Client
{
Enabled = true,
ClientName = "JS Client",
ClientId = "js",
Flow = Flows.Implicit,
RedirectUris = new List<string>
{
"http://localhost:56522"
},
AllowedCorsOrigins = new List<string>
{
"http://localhost:56522"
},
AllowAccessToAllScopes = true
}
Trying to POST the following HTTP request to token endpoint:
Content-Type:application/x-www-form-urlencoded
grant_type:password
redirect_uri:http://localhost:56522
client_id:js
username:bob
password:secret
scope:api
I get Invalid client error message and log shows:
Action returned 'IdentityServer3.Core.Results.TokenErrorResult'', Operation=ReflectedHttpActionDescriptor.ExecuteAsync
Any ideas what do I still miss?
Your request is using the password grant type, which is the OAuth Resource Owner flow, but your client is configured to use the OpenID Connect Implicit flow.
Either change your client configuration to use the Resource Owner flow, or change your request to be a valid OpenID Connect request.
For example: GET /connect/authorize?client_id=js&scope=openid api&response_type=id_token token&redirect_uri=http://localhost:56522&state=abc&nonce=xyz. This will take you to a login page.
Or better yet, use a JavaScipt library like #Jenan suggested, such as the IdentityModel oidc-client which handles these requests for you.
The ASP.NET Security Social Sample has two ways to interact with Google.
UseOAuthAuthentication
app.UseOAuthAuthentication(new OAuthOptions
{
AuthenticationScheme = "Google-AccessToken",
DisplayName = "Google-AccessToken",
ClientId = Configuration["google:clientid"],
ClientSecret = Configuration["google:clientsecret"],
CallbackPath = new PathString("/signin-google-token"),
AuthorizationEndpoint = GoogleDefaults.AuthorizationEndpoint,
TokenEndpoint = GoogleDefaults.TokenEndpoint,
Scope = { "openid", "profile", "email" },
SaveTokens = true
});
UseGoogleAuthentication
app.UseGoogleAuthentication(new GoogleOptions
{
ClientId = Configuration["google:clientid"],
ClientSecret = Configuration["google:clientsecret"],
SaveTokens = true,
Events = new OAuthEvents()
{
OnRemoteFailure = ctx =>
{
ctx.Response.Redirect("/error?FailureMessage="
+ UrlEncoder.Default.Encode(ctx.Failure.Message));
ctx.HandleResponse();
return Task.FromResult(0);
}
}
});
What is the standard name for these two types of authentication and authorization? I.e. is one OAuth and the other OpenID Connect?
When choosing to UseOAuthAuthentication, this is the result.
context
.User.Claims: []
.User.Identity.Name: null
.Authentication.GetTokenAsync("access_token"): ya29.CjAlAz3AcUnRD...
.Authentication.GetTokenAsync("refresh_token"): null
.Authentication.GetTokenAsync("token_type"): Bearer
.Authentication.GetTokenAsync("expires_at"): 2016-07-19T22:49:54...
When choosing to UseGoogleAuthentication, this is the result.
context
.User.Claims: [
nameidentifier: 10424487944...
givenname: Shaun
surname: Luttin
name: Shaun Luttin
emailaddress: admin#shaunl...
profile: https://plus.google.com/+ShaunLuttin
]
.User.Identity.Name: "Shaun Luttin"
.Authentication.GetTokenAsync("access_token"): ya29.CjAlAz3AcUnRD...
.Authentication.GetTokenAsync("refresh_token"): null
.Authentication.GetTokenAsync("token_type"): Bearer
.Authentication.GetTokenAsync("expires_at"): 2016-07-19T22:49:54...
Both UseOAuthAuthentication and UseGoogleAuthentication are OAuth. The difference is that the Google middleware sets some default OAuth options that are specific to Google and adds a GoogleHandler that gets the user profile information.
In other words,
UseOAuthAuthentication is OAuth that retrieves and access token.
UseGoogleAuthentication is OAuth with its options and flow tuned to retrieve an access code and user profile information from Google.