implementing an OAuth2 provider with Grails - grails

I'm trying to implement a Grails app that provides OAuth2 using the Spring Security OAuth2 Provider plugin. The provider app and a client app that I use to test it, are both available on GitHub.
I've followed the instructions in the plugin's docs, that explain how to implement a provider. To test it, I saved the following oauth client and user in Bootstrap.groovy
def init = { servletContext ->
def saveArgs = [flush: true, failOnError: true]
def userRole = new Role(authority: 'ROLE_USER').save(saveArgs)
def clientRole = new Role(authority: 'ROLE_CLIENT').save(saveArgs)
// create an OAuth client
new Client(
clientId: 'my-client',
authorizedGrantTypes: ['authorization_code', 'refresh_token', 'implicit', 'password', 'client_credentials'],
authorities: ['ROLE_CLIENT'],
scopes: ['read', 'write'],
redirectUris: ['http://localhost:9090/oauth-client/auth/callback']
).save(saveArgs)
// create a regular user
def user = new User(username: 'me', password: 'password').save(saveArgs)
UserRole.create user, userRole, true
}
In the client app, I click on the following link that initiates the authorization code grant flow
<g:set var="redirectUrl" value="${g.createLink(controller: 'auth', action: 'callback', absolute: true)}"/>
<h2>
OAuth Login
</h2>
I enter the username and password of the above user in the login form, then click the "Authorize" button shown in the confirmation dialog thereafter. An authorization code is successfully returned to the client app, but when it tries to exchange this for an access token the following error occurs
invalid_scope: Empty scope (either the client or the user is not allowed the requested scopes)
The code used to exchange the authorization code for an access token is shown below.
String getAccessToken(String authCode) {
def url = 'http://localhost:8080/oauth2-provider/oauth/token'
def params = [
grant_type: 'authorization_code',
code: authCode,
client_id: 'my-client'
]
new HTTPBuilder(url).request(POST, JSON) {
uri.query = params
response.success = { resp, json ->
json.access_token
}
response.failure = { resp, json ->
log.error "HTTP error code: $resp.status, status line: $resp.statusLine, "
json.each { key, value ->
log.error "$key: $value"
}
}
}
}
The link that initiates the OAuth process requests access to the read scope. This is included in the scopes property of the Client object, so there's no obvious reason why access to this scope should be prohibited.
You can reproduce the error by following these instructions:
Start the provider app with grails run-app
Start the client app on port 9090 with grails run-app -Dserver.port=9090
On the homepage of the client app click the "OAuth Login" link. This redirects to the provider app and prompts you to login. Enter the username "me", password "password", and click the "Authorize" button in the confirmation dialog that follows.

For anyone who stumbles across this in the future:
This was the result of a small bug in the plugin. The temporary workaround is to include the scope parameter in the token endpoint request with the same value as the scope parameter sent to the authorization endpoint.
See issue #64 on the plugin's GitHub repository for more details about the problem.

Related

Created a user pool client using Cognito Identity Provider Client SDK for JavaScript v3, but can't fetch token using (client_credentials) grant type

Created a user pool client using Cognito Identity Provider Client SDK for JavaScript v3
npm install #aws-sdk/client-cognito-identity-provider.
The following code shows how I created the resources server and the user pool client, using the mentioned👆 SDK...
let poolName = 'UserPool';
const client =new CognitoIdentityProviderClient({
region: process.env.COGNITO_AWS_REGION
});
// create resource server
const createResourceServerCommand = new CreateResourceServerCommand({
Name: poolName,
UserPoolId: UserPool.Id,
Identifier: 'https://localhost:8080/api/v2',
Scopes: [
{
ScopeName: 'access',
ScopeDescription: 'General access to API'
}
]
});
const { ResourceServer } = await client.send(createResourceServerCommand);
// create the user pool client
const createUserPoolClientCommand = new CreateUserPoolClientCommand({
ClientName: 'Default',
UserPoolId: UserPool.Id,
ExplicitAuthFlows: ['USER_PASSWORD_AUTH'],
GenerateSecret: true,
AllowedOAuthFlows: ['client_credentials'],
SupportedIdentityProviders: ['COGNITO'],
AllowedOAuthScopes: [ 'https://localhost:8080/api/v2/access' ]
});
const { UserPoolClient } = await client.send(createUserPoolClientCommand);
...but, I can't fetch tokens using the grant type client_credentials. Therefore getting the following error.
{
"error": "invalid_grant"
}
However, if I use AWS console to navigate to the user pool > Client > Edit the hosted UI and click on the save button without making any changes...
... I am able to fetch a token using the client_credentials grant type.
Is there any setting that I might be missing in the above code that AWS console is setting? I need the following code to automate the creation of user pools.
When I switched to the old I noticed this notification
Apparently, Oauth flows are not enabled by default. Hence adding the following attribute to the CreateUserPoolClientCommandInput object AllowedOAuthFlowsUserPoolClient: true enables it. Hope this helps some newbie like me out there.

#feathersjs/authentication-oauth2 not creating JWT and user

I cannot authenticate to a FeathersJS server using OAuth2 Facebook strategy because after Facebook grants access to user profile, the feathers-authentication-oauth2 plugin doesn't create the user into the DB and it also doesn't create the required JWT token to be authenticated when calling feathersclient.authenticate() in the client app.
I've tried to follow all documents I've found that explain how to do it, but as a good example I could select this one (https://blog.feathersjs.com/how-to-setup-oauth-flow-with-featherjs-522bdecb10a8) that is very well explained.
As a starting point I've taken the Feathers chat application explained at the documentation (https://docs.feathersjs.com/guides/chat/readme.html) after having it working properly, I've added tha OAuth2 part as explained in the Medium document. In the default.json file I've added the "facebook" authentication strategy:
"facebook": {
"clientID": "MY_CLIENT_ID",
"clientSecret": "MY_CLIENT_SECRET"
}
In the authentication.js fileI've added the configuration of the Facebook OAuth2 authentication:
const authentication = require('#feathersjs/authentication');
const jwt = require('#feathersjs/authentication-jwt');
const oauth2 = require('#feathersjs/authentication-oauth2');
const FacebookStrategy = require('passport-facebook').Strategy;
module.exports = function (app) {
const config = app.get('authentication');
// Set up authentication with the secret
app.configure(authentication(config));
app.configure(jwt());
app.configure(oauth2({
name: 'facebook',
Strategy: FacebookStrategy,
callbackURL: '/',
scope: ['public_profile', 'email'],
}));
...
And finally, in src/app.js file I've added a new "Facebook login" button that just changes window.location to '/auth/facebook' so that the OAuth2 Facebook process can begin.
After pressing the "Facebook login", I'd expect the user to be created in the NeDB DB and a valid JWT to be stored so that the feathersclient.authenticate() call would not fail.
But instead of that, the Facebook login page is properly called, and after that the browser is returned to the main page ('/'), but after that, when the main page is reloaded and the feathersclient.authenticate() is called, the server complains that there isn't any valid JWT token, so authentication fails. Also I cannot see the user created in the NeDB DB, so the supposed user and JWT creation that should be done by the feathers-authentication-oauth2 plugin is not...
I've finally made it work... I was wrongly configuring the Facebook authentication strategy, I've changed it to:
app.configure(oauth2({
name: 'facebook',
successRedirect: '/',
failureRedirect: '/',
Strategy: FacebookStrategy
}));
and now it is working.

Reproducing an ADAL.JS-authenticated request in Postman

I have a .NET Web API and a small vanilla-JS app using ADAL.js, and I've managed to make them talk nicely to each-other and authenticate correctly.
If I console.log the token returned from adalAuthContext.acquireToken() and manually enter it as Authorization: Bearer {{token}} in Postman, I can also get a valid, authenticated, response from my backend.
However, I can't figure out how to configure Postman's built-in OAuth2.0 authentication UI to get me tokens automatically. I have managed to get tokens in several ways, but none of them are accepted by the backend.
How do I configure Postman to get a token the same way the ADAL.js library does?
For completeness, here's some code:
Backend configuration:
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters { ValidAudience = "<app-id>" },
Tenant = "<tenant>",
AuthenticationType = "WebAPI"
});
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
app.UseWebApi(config);
}
ADAL.js configuration:
const backendUrl = 'http://localhost:55476';
const backendAppId = '<app-id>';
const authContext = new AuthenticationContext({
clientId: backendAppId,
tenant: '<tenant>',
endpoints: [{ [backendAppId]: backendAppId }],
cacheLocation: 'localStorage'
});
Actually making a request:
authContext.acquireToken(backendAppId, (error, token) => {
// error handling etc omitted
fetch(backendUrl, { headers: { Authorization: `Bearer ${token}` } })
.then(response => response.json())
.then(console.log)
})
So since the Azure AD v1 endpoint is not fully standards-compliant, we have to do things in a slightly weird way.
In Postman:
Select OAuth 2.0 under Authorization
Click Get new access token
Select Implicit for Grant Type
Enter your app's reply URL as the Callback URL
Enter an authorization URL similar to this: https://login.microsoftonline.com/yourtenant.onmicrosoft.com/oauth2/authorize?resource=https%3A%2F%2Fgraph.microsoft.com
Enter your app's application id/client id as the Client Id
Leave the Scope and State empty
Click Request token
If you configured it correctly, you'll get a token and Postman will configure the authorization header for you.
Now about that authorization URL.
Make sure you specify either your AAD tenant id or a verified domain name instead of yourtenant.onmicrosoft.com.
Or you can use common if your app is multi-tenant.
The resource is the most important parameter (and non-standards-compliant).
It tells AAD what API you want an access token for.
In this case I requested a token for MS Graph API, which has a resource URI of https://graph.microsoft.com.
For your own APIs, you can use either their client id or App ID URI.
Here is a screenshot of my settings:

invalid_scope error in access token for client credential flow

I am getting invalid_scope error in access token request for client credential flow. The error log states that "cannot request OpenID scopes in client credentials flow". I haven't requested for the open id scope. I don't know from where it came from. I need to generate access token using client credential flow.
Issue / Steps to reproduce the problem
API Resource definition.
public IEnumerable GetApiResources()
{
return new List {
new ApiResource
{
Name = "WidgetApi",
DisplayName = "Widget Management API",
Description = "Widget Management API Resource Access",
ApiSecrets = new List { new Secret("scopeSecret".Sha256()) },
Scopes = new List {
new Scope("WidgetApi.Read"),
new Scope("WidgetApi.Write")
}
}
};
}
Client Definition;
return new List
{
new Client
{
ClientId = "WidgetApi Client Id",
ClientName = "WidgetApi Client credential",
RequireConsent = false,
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret( clientSecret.Sha256())
},
// scopes that client has access to
AllowedScopes = { "WidgetApi.Read", "WidgetApi.Write"},
AccessTokenLifetime = 3600
};
}
Access token request body (key - value) using postman
grant_type client_credentials
response_type id_token
scope WidgetApi.Read WidgetApi.Write
client_secret xxxxxxxxxxxxxxxxxxxxxx
client_id WidgetApiClientId
Relevant parts of the log file
dbug: Microsoft.EntityFrameworkCore.Storage.Internal.SqlServerConnection[4]
Closing connection to database 'IdentityServer4Db' on server 'localhost\SQLEXPRESS'.
dbug: IdentityServer4.EntityFramework.Stores.ResourceStore[0]
Found PssUserMgtApi.Read, PssUserMgtApi.Write API scopes in database
fail: IdentityServer4.Validation.TokenRequestValidator[0]
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx cannot request OpenID scopes in client credentials flow
fail: IdentityServer4.Validation.TokenRequestValidator[0]
{
"ClientId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"ClientName": "xxxxxxxxxxxxxxxxxxxxxxxxx",
"GrantType": "client_credentials",
"Scopes": "xxxxxxxxxx.Read xxxxxxxxxxxxx.Write",
"Raw": {
"grant_type": "client_credentials",
"response_type": "id_token",
"scope": "xxxxxxxxxxxx.Read xxxxxxxxxxxxx.Write",
"client_secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"client_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 5292.2873ms 400 application/json
dbug: Microsoft.AspNetCore.Server.Kestrel[9]
Connection id "0HL51IVGKG792" completed keep alive response.
Since there is no user tagged in a client credential flow, normally, client credential is not intended to have a scope tagged to it, and many frameworks doesnt support it.
https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/ says :
scope (optional) :
Your service can support different scopes for the client credentials grant. In practice, not many services actually support this.
Check whether your client credential details are correct or not. You can also find this simple step by step explanation to configure client credential flow using this link
If you have this problem, just remove the 'openid' scope for a given client in the database in ClientScopes.
Actually the question already contains the answer:
grant_type client_credentials
response_type id_token
scope WidgetApi.Read WidgetApi.Write
client_secret xxxxxxxxxxxxxxxxxxxxxx
client_id WidgetApiClientId
The request of client_credentials type should be processed at token endpoint and must not require id_token as the flow is non-interactive. The redundant parameter is breaking the flow.
I get this error with IdentityServer4 2.1.3, but not with IdentityServer4 2.3.2. It seems, from the GitHub issues for the project, that it was fixed in 2.3:
https://github.com/IdentityServer/IdentityServer4/issues/2295#issuecomment-405164127

OAuts with Signpost - how to sign POST to get Access token

I'm working with client who provided me with somewhat vague
instructions. Here's what I'm doing (using CommonsHttpOAuthConsumer as
consumer and DefaultOAuthProvider as provider)
I'm able to get response token from doing this:
String requestToken = provider.retrieveRequestToken
(OAuth.OUT_OF_BAND);
this is in form of URL with params so I'm parsing the actual token
out for example:
https://foobar.com/oauth/login_authorize?oauth_token=XRFCGPbES3M2bYZy...
Now - the instructions that I get say:
Given the request token obtained in step 1, login with the user’s
credentials (name and password) as POST parameters and sign the
request with the request token/secret
POST https://foobar.com/oauth/login_authorize
That's where I'm having difficulties. Obviously I have to input that
requestToken somewhere so I do this (post is HttpPost that contains user credentials):
consumer.setTokenWithSecret(requestToken, SECRET);
consumer.sign(post);
It doesn't work. It actually generates 200 status but what I get is a
generic error message.
retrieveRequestToken does not return a request token, it returns an authenticationUrl that you need to send your users to so they can sign in. Request token is saved in provider object.
String authenticationUrl = provider.retrieveRequestToken( call_back_url )
Note: According to the oauth standard the users sign in on the providers site with their credentials. After they have done that you (as a consumer) can get the Access Token and after that you can access their data on the providers site.
// After user has signed in
provider.retrieveAccessToken(null)
// access token is saved in provider and provider knows which consumer uses it
// so now you can sign with your consumer and connect with the request
URL url = new URL( protected_resources_url )
HttpURLConnection request = (HttpURLConnection) url.openConnection();
consumer.sign(request)
request.connect()
If you have the user credentials you can do the authorization in your script
// Using grails and functional-tests
get(authenticationUrl)
// Image the site shows a simple form with username/password and a login button
setRedirectEnabled false
form {
username = "mario"
password = "peach"
click "login"
}
And then do retrieveRequestToken and the code mentioned above
Hope this helps you
// Jonas

Resources